diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 000000000..3f2182f9c --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,28 @@ +task: + name: freebsd-build + freebsd_instance: + matrix: + - image_family: freebsd-12-2 + - image_family: freebsd-13-0 + + prepare_script: + - pkg install -yq git cmake pkgconf jpeg-turbo mysql80-client ffmpeg libvncserver libjwt 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 --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/tests + - ./tests "~[notCI]" diff --git a/.eslintignore b/.eslintignore index 4682851df..55fe41a49 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,7 +4,8 @@ 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/bootstrap.bundle.min.js web/skins/classic/js/chosen web/skins/classic/js/dateTimePicker web/skins/classic/js/jquery-*.js @@ -13,6 +14,8 @@ web/skins/classic/js/jquery.js web/skins/classic/js/moment.js web/skins/classic/js/video.js web/tools/mootools +web/js/janus.js +web/js/ajaxQueue.js # Cannot be parsed as JS web/skins/classic/includes/export_functions.php diff --git a/.eslintrc.js b/.eslintrc.js index 64950ee0d..16cbb37ba 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,6 +3,7 @@ module.exports = { "env": { "browser": true, + "es2017": true, }, "extends": ["google"], "overrides": [{ diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..8785e55b2 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Reindent CMakeLists +6c9983155c65848a3e67976445cd20fb4fbfe108 \ No newline at end of file diff --git a/.github/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/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 610480e8e..7751de627 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,3 +1,5 @@ + **Describe Your Environment** + **If the issue concerns a camera** + **Describe the bug** + **To Reproduce** + **Expected behavior** + **Debug Logs** ``` + ``` diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 000000000..603000225 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,2 @@ +paths-ignore: + - dep/ diff --git a/.github/stale.yml b/.github/stale.yml index 9ff190b1f..4dde9b5a7 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,10 +1,10 @@ # Configuration for probot-stale - https://github.com/probot/stale # Number of days of inactivity before an issue becomes stale -daysUntilStale: 60 +daysUntilStale: 180 # Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 +daysUntilClose: 30 # Issues with these labels will never be considered stale exemptLabels: 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..dce24c367 --- /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@8.7.0 eslint-config-google@0.14.0 eslint-plugin-html@6.2.0 eslint-plugin-php-markup@6.0.0 + - 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 new file mode 100644 index 000000000..d25aa3710 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,79 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +name: "CodeQL" + +on: + push: + branches: [master] + pull_request: + # The branches below must be a subset of the branches above + branches: [master] + schedule: + - cron: '0 3 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + language: ['cpp', 'javascript'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + config-file: ./.github/codeql/codeql-config.yml + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + - name: Clean install dependencies and build + run: | + git submodule init + git submodule update --init --recursive + sudo apt-get update + sudo apt-get install 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 + sudo apt-get install libdate-manip-perl libdbd-mysql-perl libphp-serialization-perl libsys-mmap-perl + sudo apt-get install libwww-perl libdata-uuid-perl libssl-dev libcrypt-eksblowfish-perl libdata-entropy-perl + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl- + + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/create-packages.yml b/.github/workflows/create-packages.yml new file mode 100644 index 000000000..e62c5aec1 --- /dev/null +++ b/.github/workflows/create-packages.yml @@ -0,0 +1,41 @@ +name: Create packages + +on: + push: + branches: [ master ] + 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: + fetch-depth: '0' + 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 + + - name: Publish + uses: easingthemes/ssh-deploy@main + env: + SSH_PRIVATE_KEY: ${{ secrets.ZMREPO_SSH_KEY }} + ARGS: "-rltgoDzvO" + SOURCE: build/ + REMOTE_HOST: ${{ secrets.ZMREPO_HOST }} + REMOTE_USER: ${{ secrets.ZMREPO_SSH_USER }} + TARGET: debian/master/mini-dinstall/incoming/ diff --git a/.gitignore b/.gitignore index 3167e62a6..3d61b4f54 100644 --- a/.gitignore +++ b/.gitignore @@ -120,15 +120,11 @@ src/CMakeFiles/ src/cmake_install.cmake src/libzm.a src/nph-zms -src/zm_config.h -src/zm_config_defines.h -src/zma src/zmc src/zmf src/zms src/zmu -src/zoneminder-zma.8 -src/zoneminder-zma.8.gz +src/zm_rtsp_server src/zoneminder-zmc.8 src/zoneminder-zmc.8.gz src/zoneminder-zmf.8 @@ -157,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 eb0e282a2..fd07ba226 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,10 @@ [submodule "web/api/app/Plugin/Crud"] path = web/api/app/Plugin/Crud - url = https://github.com/ZoneMinder/crud.git + url = https://github.com/FriendsOfCake/crud.git branch = 3.0 [submodule "web/api/app/Plugin/CakePHP-Enum-Behavior"] path = web/api/app/Plugin/CakePHP-Enum-Behavior - url = https://github.com/ZoneMinder/CakePHP-Enum-Behavior.git + Url = https://github.com/ZoneMinder/CakePHP-Enum-Behavior.git +[submodule "dep/RtspServer"] + path = dep/RtspServer + url = https://github.com/ZoneMinder/RtspServer diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..38593578d --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,9 @@ +version: 2 + +build: + os: "ubuntu-20.04" + tools: + python: "3.8" + +sphinx: + fail_on_warning: true diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7cfe96854..000000000 --- a/.travis.yml +++ /dev/null @@ -1,74 +0,0 @@ -language: cpp -sudo: required -dist: xenial -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 - - 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=el DIST=7 - - SMPFLAGS=-j4 OS=el DIST=8 DOCKER_REPO=knnniggett/packpack - - SMPFLAGS=-j4 OS=fedora DIST=30 - - SMPFLAGS=-j4 OS=fedora DIST=31 - - SMPFLAGS=-j4 OS=ubuntu DIST=trusty DOCKER_REPO=iconzm/packpack - - SMPFLAGS=-j4 OS=ubuntu DIST=xenial DOCKER_REPO=iconzm/packpack - - SMPFLAGS=-j4 OS=ubuntu DIST=bionic DOCKER_REPO=iconzm/packpack - - SMPFLAGS=-j4 OS=ubuntu DIST=disco DOCKER_REPO=iconzm/packpack - - SMPFLAGS=-j4 OS=ubuntu DIST=eoan DOCKER_REPO=iconzm/packpack - - SMPFLAGS=-j4 OS=debian DIST=jessie DOCKER_REPO=iconzm/packpack - - SMPFLAGS=-j4 OS=debian DIST=stretch DOCKER_REPO=iconzm/packpack - - SMPFLAGS=-j4 OS=debian DIST=buster DOCKER_REPO=iconzm/packpack - - SMPFLAGS=-j4 OS=ubuntu DIST=trusty ARCH=i386 - - SMPFLAGS=-j4 OS=ubuntu DIST=xenial ARCH=i386 - - SMPFLAGS=-j4 OS=ubuntu DIST=disco ARCH=i386 - - SMPFLAGS=-j4 OS=debian DIST=buster ARCH=i386 - - SMPFLAGS=-j4 OS=debian DIST=stretch ARCH=i386 - - SMPFLAGS=-j4 OS=eslint DIST=eslint - -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 b43680b0b..6337e50c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,283 +2,288 @@ # Created by mastertheknife (Kfir Itzhak) # For more information and installation, see the INSTALL file # -cmake_minimum_required (VERSION 2.8.7) -project (zoneminder) -file (STRINGS "version" zoneminder_VERSION) +cmake_minimum_required(VERSION 3.5.0) +project(zoneminder) +file(STRINGS "version" zoneminder_VERSION) # make API version a minor of ZM version set(zoneminder_API_VERSION "${zoneminder_VERSION}.1") # Make sure the submodules are there -if( NOT EXISTS "${CMAKE_SOURCE_DIR}/web/api/app/Plugin/Crud/Lib/CrudControllerTrait.php" ) -message( SEND_ERROR "The git submodules are not available. Please run -git submodule update --init --recursive") -endif( NOT EXISTS "${CMAKE_SOURCE_DIR}/web/api/app/Plugin/Crud/Lib/CrudControllerTrait.php" ) +if(NOT EXISTS "${CMAKE_SOURCE_DIR}/web/api/app/Plugin/Crud/Lib/CrudControllerTrait.php") + message(SEND_ERROR "The git submodules are not available. Please run git submodule update --init --recursive") +endif() # CMake does not allow out-of-source build if CMakeCache.exists # in the source folder. Abort and notify the user -if( - (NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - AND (EXISTS "${CMAKE_SOURCE_DIR}/CMakeCache.txt")) - message(FATAL_ERROR " You are attempting to do an out-of-source build, - but a cmake cache file for an in-source build exists. Please delete - the file CMakeCache.txt from the source folder to proceed.") -endif( - (NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - AND (EXISTS "${CMAKE_SOURCE_DIR}/CMakeCache.txt")) +if((NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) AND (EXISTS "${CMAKE_SOURCE_DIR}/CMakeCache.txt")) + message(FATAL_ERROR " You are attempting to do an out-of-source build, + but a cmake cache file for an in-source build exists. Please delete + the file CMakeCache.txt from the source folder to proceed.") +endif() # Default build type. To change the build type, # use the CMAKE_BUILD_TYPE configuration option. if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE - Release CACHE STRING "Build type: Release or Debug" FORCE) -endif(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type: Release or Debug" FORCE) +endif() # Can assist in troubleshooting #set(CMAKE_VERBOSE_MAKEFILE ON) #set(CMAKE_INSTALL_ALWAYS ON) -# Host OS Check -set(HOST_OS "") -if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - set(HOST_OS "linux") -endif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") -if(${CMAKE_SYSTEM_NAME} MATCHES ".*(SunOS|Solaris).*") - set(HOST_OS "solaris") - set(SOLARIS 1) -endif(${CMAKE_SYSTEM_NAME} MATCHES ".*(SunOS|Solaris).*") -if(${CMAKE_SYSTEM_NAME} MATCHES ".*BSD.*") - set(HOST_OS "BSD") - set(BSD 1) -endif(${CMAKE_SYSTEM_NAME} MATCHES ".*BSD.*") -if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") - set(HOST_OS "darwin") -endif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") -if(NOT HOST_OS) - message(FATAL_ERROR - "ZoneMinder was unable to deterimine the host OS. Please report this. Value of CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}") -endif(NOT HOST_OS) - -set (CMAKE_CXX_STANDARD 11) -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") # Default CLFAGS and CXXFLAGS: -set(CMAKE_C_FLAGS_RELEASE "-Wall -D__STDC_CONSTANT_MACROS -O2") -set(CMAKE_CXX_FLAGS_RELEASE "-Wall -D__STDC_CONSTANT_MACROS -O2") -set(CMAKE_C_FLAGS_DEBUG "-Wall -D__STDC_CONSTANT_MACROS -g") -set(CMAKE_CXX_FLAGS_DEBUG "-Wall -D__STDC_CONSTANT_MACROS -g") +set(CMAKE_C_FLAGS_RELEASE "-O2") +set(CMAKE_CXX_FLAGS_RELEASE "-O2") +set(CMAKE_C_FLAGS_DEBUG "-g") +set(CMAKE_CXX_FLAGS_DEBUG "-g") +set(CMAKE_C_FLAGS_OPTIMISED "-O3") +set(CMAKE_CXX_FLAGS_OPTIMISED "-O3") set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") +include(ConfigureBaseTargets) +include(CheckPlatform) + # GCC below 6.0 doesn't support __target__("fpu=neon") attribute, required for compiling ARM Neon code, otherwise compilation fails. # Must use -mfpu=neon compiler flag instead, but only do that for processors that support neon, otherwise strip the neon code alltogether, # because passing -fmpu=neon is unsafe to processors that don't support neon # Arm neon support only tested on Linux. If your arm hardware is running a non-Linux distro and is using gcc then contact us. -IF (CMAKE_SYSTEM_NAME MATCHES "Linux") - string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" ZM_SYSTEM_PROC) - IF((ZM_SYSTEM_PROC STREQUAL "") OR (ZM_SYSTEM_PROC STREQUAL "unknown")) - execute_process(COMMAND uname -m OUTPUT_VARIABLE ZM_SYSTEM_PROC ERROR_VARIABLE ZM_SYSTEM_PROC_ERR) - - # maybe make the following error checks fatal - IF(ZM_SYSTEM_PROC_ERR) - message(WARNING "\nAn error occurred while attempting to determine the system processor:\n${ZM_SYSTEM_PROC_ERR}") - ENDIF(ZM_SYSTEM_PROC_ERR) - IF(NOT ZM_SYSTEM_PROC) - message(WARNING "\nUnable to determine the system processor. This may cause a build failure.\n") - ENDIF(ZM_SYSTEM_PROC) - - ENDIF((ZM_SYSTEM_PROC STREQUAL "") OR (ZM_SYSTEM_PROC STREQUAL "unknown")) - - IF(ZM_SYSTEM_PROC MATCHES "^arm") - IF(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) - EXEC_PROGRAM(grep ARGS " neon " "/proc/cpuinfo" OUTPUT_VARIABLE neonoutput RETURN_VALUE neonresult) - IF(neonresult EQUAL 0) - set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mfpu=neon") - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mfpu=neon") - set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -mfpu=neon") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -mfpu=neon") - ELSE(neonresult EQUAL 0) - add_definitions(-DZM_STRIP_NEON=1) - message(STATUS "ARM Neon is not available on this processor. Neon functions will be absent") - ENDIF(neonresult EQUAL 0) - ENDIF(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) - ENDIF(ZM_SYSTEM_PROC MATCHES "^arm") -ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux") +if(ZM_SYSTEM_PROC MATCHES "^arm") + if(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) + exec_program(grep ARGS " neon " "/proc/cpuinfo" OUTPUT_VARIABLE neonoutput RETURN_VALUE neonresult) + if(neonresult EQUAL 0) + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mfpu=neon") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mfpu=neon") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -mfpu=neon") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -mfpu=neon") + else() + add_definitions(-DZM_STRIP_NEON=1) + message(STATUS "ARM Neon is not available on this processor. Neon functions will be absent") + endif() + endif() +endif() # Modules that we need: -include (GNUInstallDirs) -include (CheckIncludeFile) -include (CheckIncludeFiles) -include (CheckFunctionExists) -include (CheckPrototypeDefinition_fixed) -include (CheckTypeSize) -include (CheckStructHasMember) -include (CheckSendfile) +include(GNUInstallDirs) +include(CheckIncludeFile) +include(CheckIncludeFiles) +include(CheckFunctionExists) +include(CheckTypeSize) +include(CheckSendfile) # Configuration options -mark_as_advanced( - FORCE ZM_EXTRA_LIBS - ZM_MYSQL_ENGINE - ZM_NO_MMAP - CMAKE_INSTALL_FULL_BINDIR - ZM_PERL_MM_PARMS - ZM_PERL_SEARCH_PATH - ZM_TARGET_DISTRO - ZM_PATH_MAP - ZM_PATH_ARP - ZM_CONFIG_DIR - ZM_CONFIG_SUBDIR - ZM_SYSTEMD - ZM_MANPAGE_DEST_PREFIX) +mark_as_advanced( + FORCE ZM_EXTRA_LIBS + ZM_MYSQL_ENGINE + ZM_NO_MMAP + CMAKE_INSTALL_FULL_BINDIR + ZM_PERL_MM_PARMS + ZM_PERL_SEARCH_PATH + ZM_TARGET_DISTRO + ZM_PATH_MAP + ZM_PATH_ARP + ZM_PATH_ARP_SCAN + ZM_CONFIG_DIR + ZM_CONFIG_SUBDIR + ZM_SYSTEMD + ZM_MANPAGE_DEST_PREFIX) -set(ZM_RUNDIR "/var/run/zm" CACHE PATH - "Location of transient process files, default: /var/run/zm") -set(ZM_SOCKDIR "/var/run/zm" CACHE PATH - "Location of Unix domain socket files, default /var/run/zm") -set(ZM_TMPDIR "/var/tmp/zm" CACHE PATH - "Location of temporary files, default: /tmp/zm") -set(ZM_LOGDIR "/var/log/zm" CACHE PATH - "Location of generated log files, default: /var/log/zm") -set(ZM_WEBDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/zoneminder/www" CACHE PATH - "Location of the web files, default: /${CMAKE_INSTALL_DATADIR}/zoneminder/www") -set(ZM_CGIDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBEXECDIR}/zoneminder/cgi-bin" CACHE PATH - "Location of the cgi-bin files, default: /${CMAKE_INSTALL_LIBEXECDIR}/zoneminder/cgi-bin") -set(ZM_CACHEDIR "/var/cache/zoneminder" CACHE PATH - "Location of the web server cache busting files, default: /var/cache/zoneminder") -set(ZM_CONTENTDIR "/var/lib/zoneminder" CACHE PATH - "Location of dynamic content (events and images), default: /var/lib/zoneminder") -set(ZM_DB_HOST "localhost" CACHE STRING - "Hostname where ZoneMinder database located, default: localhost") -set(ZM_DB_NAME "zm" CACHE STRING - "Name of ZoneMinder database, default: zm") -set(ZM_DB_USER "zmuser" CACHE STRING - "Name of ZoneMinder database user, default: zmuser") -set(ZM_DB_PASS "zmpass" CACHE STRING - "Password of ZoneMinder database user, default: zmpass") -set(ZM_WEB_USER "" CACHE STRING - "The user apache or the local web server runs on. Leave empty for automatic detection. +option(ENABLE_WERROR "Fail the build if a compiler warning is emitted" 0) +option(BUILD_TEST_SUITE "Build the test suite" 0) +option(BUILD_MAN "Build man pages" 1) +option(ASAN "DEBUGGING: Build with AddressSanitizer (ASan) support" 0) +option(TSAN "DEBUGGING: Build with ThreadSanitizer (TSan) support" 0) + +if(ASAN AND TSAN) + message(FATAL_ERROR "ASAN and TSAN options are mutually exclusive") +endif() + +set(ZM_RUNDIR "/var/run/zm" CACHE PATH + "Location of transient process files, default: /var/run/zm") +set(ZM_SOCKDIR "/var/run/zm" CACHE PATH + "Location of Unix domain socket files, default /var/run/zm") +set(ZM_TMPDIR "/var/tmp/zm" CACHE PATH + "Location of temporary files, default: /tmp/zm") +set(ZM_LOGDIR "/var/log/zm" CACHE PATH + "Location of generated log files, default: /var/log/zm") +set(ZM_WEBDIR "${CMAKE_INSTALL_FULL_DATADIR}/zoneminder/www" CACHE PATH + "Location of the web files, default: /${CMAKE_INSTALL_DATADIR}/zoneminder/www") +set(ZM_CGIDIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/zoneminder/cgi-bin" CACHE PATH + "Location of the cgi-bin files, default: /${CMAKE_INSTALL_LIBEXECDIR}/zoneminder/cgi-bin") +set(ZM_CACHEDIR "/var/cache/zoneminder" CACHE PATH + "Location of the web server cache busting files, default: /var/cache/zoneminder") +set(ZM_CONTENTDIR "/var/lib/zoneminder" CACHE PATH + "Location of dynamic content (events and images), default: /var/lib/zoneminder") +set(ZM_FONTDIR "${CMAKE_INSTALL_FULL_DATADIR}/zoneminder/fonts" CACHE PATH + "Location of the font files used for timestamping, default: /${CMAKE_INSTALL_DATADIR}/zoneminder/fonts") + +set(ZM_DB_HOST "localhost" CACHE STRING + "Hostname where ZoneMinder database located, default: localhost") +set(ZM_DB_NAME "zm" CACHE STRING + "Name of ZoneMinder database, default: zm") +set(ZM_DB_USER "zmuser" CACHE STRING + "Name of ZoneMinder database user, default: zmuser") +set(ZM_DB_PASS "zmpass" CACHE STRING + "Password of ZoneMinder database user, default: zmpass") +set(ZM_WEB_USER "" CACHE STRING + "The user apache or the local web server runs on. Leave empty for automatic detection. If that fails, you can use this variable to force") -set(ZM_WEB_GROUP "" CACHE STRING - "The group apache or the local web server runs on, +set(ZM_WEB_GROUP "" CACHE STRING + "The group apache or the local web server runs on, Leave empty to be the same as the web user") -set(ZM_DIR_EVENTS "${ZM_CONTENTDIR}/events" CACHE PATH - "Location where events are recorded to, default: ZM_CONTENTDIR/events") +set(ZM_DIR_EVENTS "${ZM_CONTENTDIR}/events" CACHE PATH + "Location where events are recorded to, default: ZM_CONTENTDIR/events") set(ZM_DIR_SOUNDS "sounds" CACHE PATH - "Location to look for optional sound files, default: sounds") + "Location to look for optional sound files, default: sounds") set(ZM_PATH_ZMS "/cgi-bin/nph-zms" CACHE PATH - "Web url to zms streaming server, default: /cgi-bin/nph-zms") + "Web url to zms streaming server, default: /cgi-bin/nph-zms") set(ZM_PATH_SHUTDOWN "/sbin/shutdown" CACHE PATH - "Path to shutdown binary, default: /sbin/shutdown") + "Path to shutdown binary, default: /sbin/shutdown") # Advanced set(ZM_PATH_MAP "/dev/shm" CACHE PATH - "Location to save mapped memory files, default: /dev/shm") + "Location to save mapped memory files, default: /dev/shm") set(ZM_PATH_ARP "" CACHE PATH - "Full path to compatible arp binary. Leave empty for automatic detection.") -set(ZM_CONFIG_DIR "/${CMAKE_INSTALL_SYSCONFDIR}" CACHE PATH - "Location of ZoneMinder configuration, default system config directory") -set(ZM_CONFIG_SUBDIR "${ZM_CONFIG_DIR}/conf.d" CACHE PATH - "Location of ZoneMinder configuration subfolder, default: ZM_CONFIG_DIR/conf.d") -set(ZM_EXTRA_LIBS "" CACHE STRING - "A list of optional libraries, separated by semicolons, e.g. ssl;theora") -set(ZM_MYSQL_ENGINE "InnoDB" CACHE STRING - "MySQL engine to use with database, default: InnoDB") -set(ZM_NO_MMAP "OFF" CACHE BOOL - "Set to ON to not use mmap shared memory. Shouldn't be enabled unless you + "Full path to compatible arp binary. Leave empty for automatic detection.") +set(ZM_PATH_ARP_SCAN "" CACHE PATH + "Full path to compatible scan_arp binary. Leave empty for automatic detection.") +set(ZM_CONFIG_DIR "/${CMAKE_INSTALL_SYSCONFDIR}" CACHE PATH + "Location of ZoneMinder configuration, default system config directory") +set(ZM_CONFIG_SUBDIR "${ZM_CONFIG_DIR}/conf.d" CACHE PATH + "Location of ZoneMinder configuration subfolder, default: ZM_CONFIG_DIR/conf.d") +set(ZM_EXTRA_LIBS "" CACHE STRING + "A list of optional libraries, separated by semicolons, e.g. ssl;theora") +set(ZM_MYSQL_ENGINE "InnoDB" CACHE STRING + "MySQL engine to use with database, default: InnoDB") +set(ZM_NO_MMAP "OFF" CACHE BOOL + "Set to ON to not use mmap shared memory. Shouldn't be enabled unless you experience problems with the shared memory. default: OFF") -set(ZM_NO_LIBVLC "OFF" CACHE BOOL -"Set to ON to skip libvlc checks and force building ZM without libvlc. default: OFF") -set(ZM_NO_CURL "OFF" CACHE BOOL - "Set to ON to skip cURL checks and force building ZM without cURL. default: OFF") -set(ZM_NO_X10 "OFF" CACHE BOOL - "Set to ON to build ZoneMinder without X10 support. default: OFF") -set(ZM_ONVIF "ON" CACHE BOOL - "Set to ON to enable basic ONVIF support. This is EXPERIMENTAL and may not +set(ZM_NO_LIBVLC "OFF" CACHE BOOL + "Set to ON to skip libvlc checks and force building ZM without libvlc. default: OFF") +set(ZM_NO_LIBVNC "OFF" CACHE BOOL + "Set to ON to skip libvnc checks and force building ZM without libvnc. default: OFF") +set(ZM_NO_CURL "OFF" CACHE BOOL + "Set to ON to skip cURL checks and force building ZM without cURL. default: OFF") +set(ZM_NO_X10 "OFF" CACHE BOOL + "Set to ON to build ZoneMinder without X10 support. default: OFF") +set(ZM_ONVIF "ON" CACHE BOOL + "Set to ON to enable basic ONVIF support. This is EXPERIMENTAL and may not work with all cameras claiming to be ONVIF compliant. default: ON") -set(ZM_PERL_MM_PARMS INSTALLDIRS=vendor NO_PACKLIST=1 NO_PERLLOCAL=1 CACHE STRING - "By default, ZoneMinder's Perl modules are installed into the Vendor folders, +set(ZM_NO_PCRE "OFF" CACHE BOOL + "Set to ON to skip libpcre3 checks and force building ZM without libpcre3. default: OFF") +set(ZM_NO_RTSPSERVER "OFF" CACHE BOOL + "Set to ON to skip building ZM with rtsp server support. default: OFF") +set(ZM_PERL_MM_PARMS INSTALLDIRS=vendor NO_PACKLIST=1 NO_PERLLOCAL=1 CACHE STRING + "By default, ZoneMinder's Perl modules are installed into the Vendor folders, as defined by your installation of Perl. You can change that here. Consult Perl's MakeMaker documentation for a definition of acceptable parameters. If you set this to something that causes the modules to be installed outside Perl's normal search path, then you will also need to set ZM_PERL_SEARCH_PATH accordingly.") -set(ZM_PERL_SEARCH_PATH "" CACHE PATH - "Use to add a folder to your Perl's search path. This will need to be set in cases +set(ZM_PERL_SEARCH_PATH "" CACHE PATH + "Use to add a folder to your Perl's search path. This will need to be set in cases where ZM_PERL_MM_PARMS has been modified such that ZoneMinder's Perl modules are installed outside Perl's default search path.") -set(ZM_TARGET_DISTRO "" CACHE STRING - "Build ZoneMinder for a specific distribution. Currently, valid names are: fc27, fc26, el7, OS13, FreeBSD") -set(ZM_SYSTEMD "OFF" CACHE BOOL - "Set to ON to force building ZM with systemd support. default: OFF") -set(ZM_MANPAGE_DEST_PREFIX "share/man" CACHE PATH - "Relative path used to install ZoneMinder's Man pages into a - non-standard folder. Most Linux users will not need to change this. - BSD users may need to set this.") +set(ZM_TARGET_DISTRO "" CACHE STRING + "Build ZoneMinder for a specific distribution. Currently, valid names are: fc27, fc26, el7, OS13, FreeBSD") +set(ZM_SYSTEMD "OFF" CACHE BOOL + "Set to ON to force building ZM with systemd support. default: OFF") +set(ZM_MANPAGE_DEST_PREFIX "share/man" CACHE PATH + "Relative path used to install ZoneMinder's Man pages into a + non-standard folder. Most Linux users will not need to change this. + BSD users may need to set this.") +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") - set(ZM_TMPDIR "/var/lib/zoneminder/temp") - set(ZM_LOGDIR "/var/log/zoneminder") - set(ZM_CONFIG_DIR "/etc/zm") - set(ZM_CONFIG_SUBDIR "/etc/zm/conf.d") - set(ZM_WEBDIR "/usr/share/zoneminder/www") - set(ZM_CGIDIR "/usr/libexec/zoneminder/cgi-bin") - set(ZM_DIR_EVENTS "/var/lib/zoneminder/events") - set(ZM_PATH_ZMS "/cgi-bin-zm/nph-zms") + set(ZM_RUNDIR "/var/run/zoneminder") + set(ZM_SOCKDIR "/var/lib/zoneminder/sock") + set(ZM_TMPDIR "/var/lib/zoneminder/temp") + set(ZM_LOGDIR "/var/log/zoneminder") + set(ZM_CONFIG_DIR "/etc/zm") + set(ZM_CONFIG_SUBDIR "/etc/zm/conf.d") + set(ZM_WEBDIR "/usr/share/zoneminder/www") + set(ZM_CGIDIR "/usr/libexec/zoneminder/cgi-bin") + set(ZM_DIR_EVENTS "/var/lib/zoneminder/events") + set(ZM_PATH_ZMS "/cgi-bin-zm/nph-zms") elseif(ZM_TARGET_DISTRO STREQUAL "OS13") - set(ZM_RUNDIR "/var/run/zoneminder") - set(ZM_TMPDIR "/var/run/zoneminder") - set(ZM_CONTENTDIR "/var/run/zoneminder") - set(ZM_LOGDIR "/var/log/zoneminder") - set(ZM_WEB_USER "wwwrun") - set(ZM_WEB_GROUP "www") - set(ZM_WEBDIR "/srv/www/htdocs/zoneminder") - set(ZM_CGIDIR "/srv/www/cgi-bin") + set(ZM_RUNDIR "/var/run/zoneminder") + set(ZM_TMPDIR "/var/run/zoneminder") + set(ZM_CONTENTDIR "/var/run/zoneminder") + set(ZM_LOGDIR "/var/log/zoneminder") + set(ZM_WEB_USER "wwwrun") + set(ZM_WEB_GROUP "www") + set(ZM_WEBDIR "/srv/www/htdocs/zoneminder") + set(ZM_CGIDIR "/srv/www/cgi-bin") elseif(ZM_TARGET_DISTRO STREQUAL "FreeBSD") - set(ZM_RUNDIR "/var/run/zm") - set(ZM_SOCKDIR "/var/run/zm") - set(ZM_TMPDIR "/var/tmp/zm") - set(ZM_CONTENTDIR "/usr/local/var/lib/zoneminder") - set(ZM_WEB_USER "www") - set(ZM_WEB_GROUP "www") - set(ZM_CONFIG_DIR "/usr/local/etc/zm") - set(ZM_CONFIG_SUBDIR "/usr/local/etc/zm/conf.d") - set(ZM_WEBDIR "/usr/local/share/zoneminder/www") - set(ZM_CGIDIR "/usr/local/libexec/zoneminder/cgi-bin") - set(ZM_PERL_MM_PARMS "INSTALLDIRS=site") -endif((ZM_TARGET_DISTRO MATCHES "^el") OR (ZM_TARGET_DISTRO MATCHES "^fc")) + set(ZM_RUNDIR "/var/run/zm") + set(ZM_SOCKDIR "/var/run/zm") + set(ZM_TMPDIR "/var/tmp/zm") + set(ZM_CONTENTDIR "/usr/local/var/lib/zoneminder") + set(ZM_WEB_USER "www") + set(ZM_WEB_GROUP "www") + set(ZM_CONFIG_DIR "/usr/local/etc/zm") + set(ZM_CONFIG_SUBDIR "/usr/local/etc/zm/conf.d") + set(ZM_WEBDIR "/usr/local/share/zoneminder/www") + set(ZM_CGIDIR "/usr/local/libexec/zoneminder/cgi-bin") + set(ZM_PERL_MM_PARMS "INSTALLDIRS=site") +endif() + +if(BUILD_MAN) + message(STATUS "Building man pages: Yes (default)") + set(ZM_PERL_MM_PARMS_FULL ${ZM_PERL_MM_PARMS}) +else() + message(STATUS "Building man pages: No") + list(APPEND ZM_PERL_MM_PARMS_FULL ${ZM_PERL_MM_PARMS} + "INSTALLMAN1DIR=none" + "INSTALLMAN3DIR=none") +endif() # Required for certain checks to work -set(CMAKE_EXTRA_INCLUDE_FILES - ${CMAKE_EXTRA_INCLUDE_FILES} stdio.h stdlib.h math.h signal.h - ) -# Required for including headers from the this folder -include_directories("${CMAKE_BINARY_DIR}") +set(CMAKE_EXTRA_INCLUDE_FILES ${CMAKE_EXTRA_INCLUDE_FILES} stdio.h stdlib.h math.h signal.h) # This is required to enable searching in lib64 (if exists), do not change set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS ON) # Set the systemd flag if systemd is autodetected or ZM_SYSTEMD has been set if(ZM_SYSTEMD OR (IS_DIRECTORY /usr/lib/systemd/system) OR (IS_DIRECTORY /lib/systemd/system)) - set(WITH_SYSTEMD 1) -endif(ZM_SYSTEMD OR (IS_DIRECTORY /usr/lib/systemd/system) OR (IS_DIRECTORY /lib/systemd/system)) + set(WITH_SYSTEMD 1) +endif() # System checks -check_include_file("libv4l1-videodev.h" HAVE_LIBV4L1_VIDEODEV_H) -if(NOT HAVE_LIBV4L1_VIDEODEV_H) - check_include_file("linux/videodev.h" HAVE_LINUX_VIDEODEV_H) -endif(NOT HAVE_LIBV4L1_VIDEODEV_H) -check_include_file("linux/videodev2.h" HAVE_LINUX_VIDEODEV2_H) check_include_file("execinfo.h" HAVE_EXECINFO_H) -if (HAVE_EXECINFO_H) +if(HAVE_EXECINFO_H) check_function_exists("backtrace" HAVE_DECL_BACKTRACE) - if (NOT HAVE_DECL_BACKTRACE) - find_library (EXECINFO_LIBRARY NAMES execinfo) - if (EXECINFO_LIBRARY) - list(APPEND ZM_BIN_LIBS "-lexecinfo") - endif (EXECINFO_LIBRARY) - endif (NOT HAVE_DECL_BACKTRACE) + if(NOT HAVE_DECL_BACKTRACE) + find_library(EXECINFO_LIBRARY NAMES execinfo) + if(EXECINFO_LIBRARY) + list(APPEND ZM_BIN_LIBS "-lexecinfo") + endif() + endif() check_function_exists("backtrace_symbols" HAVE_DECL_BACKTRACE_SYMBOLS) -endif (HAVE_EXECINFO_H) +endif() check_include_file("ucontext.h" HAVE_UCONTEXT_H) check_include_file("sys/sendfile.h" HAVE_SYS_SENDFILE_H) check_include_file("sys/syscall.h" HAVE_SYS_SYSCALL_H) @@ -290,550 +295,366 @@ check_type_size("ucontext_t" HAVE_UCONTEXT_T) # *** LIBRARY CHECKS *** -if (UNIX) - include (CheckLibraryExists) - CHECK_LIBRARY_EXISTS(rt clock_gettime "time.h" HAVE_CLOCK_GETTIME) - if(NOT HAVE_CLOCK_GETTIME) - message(FATAL_ERROR "clock_gettime not found") - else(NOT HAVE_CLOCK_GETTIME) - list(APPEND ZM_BIN_LIBS "-lrt") - endif(NOT HAVE_CLOCK_GETTIME) -endif(UNIX) +if(UNIX) + include(CheckLibraryExists) + CHECK_LIBRARY_EXISTS(rt clock_gettime "time.h" HAVE_CLOCK_GETTIME) + if(NOT HAVE_CLOCK_GETTIME) + message(FATAL_ERROR "clock_gettime not found") + else() + list(APPEND ZM_BIN_LIBS "-lrt") + endif() +endif() # zlib find_package(ZLIB) if(ZLIB_FOUND) - set(HAVE_LIBZLIB 1) - list(APPEND ZM_BIN_LIBS "${ZLIB_LIBRARIES}") - include_directories("${ZLIB_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${ZLIB_INCLUDE_DIR}") - check_include_file("zlib.h" HAVE_ZLIB_H) - set(optlibsfound "${optlibsfound} zlib") -else(ZLIB_FOUND) - set(optlibsnotfound "${optlibsnotfound} zlib") -endif(ZLIB_FOUND) + set(HAVE_LIBZLIB 1) + list(APPEND ZM_BIN_LIBS "${ZLIB_LIBRARIES}") + include_directories("${ZLIB_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${ZLIB_INCLUDE_DIR}") + check_include_file("zlib.h" HAVE_ZLIB_H) + set(optlibsfound "${optlibsfound} zlib") +else() + set(optlibsnotfound "${optlibsnotfound} zlib") +endif() # Do not check for cURL if ZM_NO_CURL is on if(NOT ZM_NO_CURL) - # cURL - find_package(CURL) - if(CURL_FOUND) - set(HAVE_LIBCURL 1) - list(APPEND ZM_BIN_LIBS ${CURL_LIBRARIES}) - include_directories(${CURL_INCLUDE_DIRS}) - set(CMAKE_REQUIRED_INCLUDES ${CURL_INCLUDE_DIRS}) - check_include_file("curl/curl.h" HAVE_CURL_CURL_H) - set(optlibsfound "${optlibsfound} cURL") - else(CURL_FOUND) - set(optlibsnotfound "${optlibsnotfound} cURL") - endif(CURL_FOUND) -endif(NOT ZM_NO_CURL) + # cURL + find_package(CURL REQUIRED) + if(CURL_FOUND) + set(HAVE_LIBCURL 1) + list(APPEND ZM_BIN_LIBS ${CURL_LIBRARIES}) + include_directories(${CURL_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_INCLUDES ${CURL_INCLUDE_DIRS}) + check_include_file("curl/curl.h" HAVE_CURL_CURL_H) + set(optlibsfound "${optlibsfound} cURL") + else() + set(optlibsnotfound "${optlibsnotfound} cURL") + endif() +endif() # jpeg find_package(JPEG) if(JPEG_FOUND) - set(HAVE_LIBJPEG 1) - list(APPEND ZM_BIN_LIBS "${JPEG_LIBRARIES}") - #link_directories(${JPEG_LIBRARY}) - include_directories("${JPEG_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${JPEG_INCLUDE_DIR}") - check_include_files("stdio.h;jpeglib.h" HAVE_JPEGLIB_H) - if(NOT HAVE_JPEGLIB_H) - message(FATAL_ERROR - "ZoneMinder requires libjpeg headers - check that libjpeg development packages are installed") - endif(NOT HAVE_JPEGLIB_H) -else(JPEG_FOUND) - message(FATAL_ERROR - "ZoneMinder requires jpeg but it was not found on your system") -endif(JPEG_FOUND) + set(HAVE_LIBJPEG 1) + list(APPEND ZM_BIN_LIBS "${JPEG_LIBRARIES}") + #link_directories(${JPEG_LIBRARY}) + include_directories("${JPEG_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${JPEG_INCLUDE_DIR}") + check_include_files("stdio.h;jpeglib.h" HAVE_JPEGLIB_H) + if(NOT HAVE_JPEGLIB_H) + message(FATAL_ERROR + "ZoneMinder requires libjpeg headers - check that libjpeg development packages are installed") + endif() +else() + message(FATAL_ERROR + "ZoneMinder requires jpeg but it was not found on your system") +endif() +# libjwt +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 +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}") + find_path(GNUTLS_INCLUDE_DIR gnutls/gnutls.h) + if(GNUTLS_INCLUDE_DIR) + include_directories("${GNUTLS_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE GNUTLS_LIBRARIES GNUTLS_INCLUDE_DIR) + set(optlibsfound "${optlibsfound} GnuTLS") + else() + set(optlibsnotfound "${optlibsnotfound} GnuTLS") + endif() # OpenSSL -find_package(OpenSSL) -if(OPENSSL_FOUND) - set(HAVE_LIBOPENSSL 1) - set(HAVE_LIBCRYPTO 1) - list(APPEND ZM_BIN_LIBS "${OPENSSL_LIBRARIES}") - include_directories("${OPENSSL_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") - check_include_file("openssl/md5.h" HAVE_OPENSSL_MD5_H) - set(optlibsfound "${optlibsfound} OpenSSL") -else(OPENSSL_FOUND) - set(optlibsnotfound "${optlibsnotfound} OpenSSL") -endif(OPENSSL_FOUND) +elseif (${ZM_CRYPTO_BACKEND} STREQUAL "openssl") + find_package(OpenSSL REQUIRED) + if(OPENSSL_FOUND) + set(HAVE_LIBOPENSSL 1) + list(APPEND ZM_BIN_LIBS "${OPENSSL_LIBRARIES}") + include_directories("${OPENSSL_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") + set(optlibsfound "${optlibsfound} OpenSSL") + else() + set(optlibsnotfound "${optlibsnotfound} OpenSSL") + endif() +endif() # pthread (using find_library and find_path) find_library(PTHREAD_LIBRARIES pthread) if(PTHREAD_LIBRARIES) - set(HAVE_LIBPTHREAD 1) - list(APPEND ZM_BIN_LIBS "${PTHREAD_LIBRARIES}") - find_path(PTHREAD_INCLUDE_DIR pthread.h) - if(PTHREAD_INCLUDE_DIR) - include_directories("${PTHREAD_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${PTHREAD_INCLUDE_DIR}") - endif(PTHREAD_INCLUDE_DIR) - mark_as_advanced(FORCE PTHREAD_LIBRARIES PTHREAD_INCLUDE_DIR) - check_include_file("pthread.h" HAVE_PTHREAD_H) - if(NOT HAVE_PTHREAD_H) - message(FATAL_ERROR - "ZoneMinder requires pthread headers - check that pthread development packages are installed") - endif(NOT HAVE_PTHREAD_H) -else(PTHREAD_LIBRARIES) - message(FATAL_ERROR - "ZoneMinder requires pthread but it was not found on your system") -endif(PTHREAD_LIBRARIES) + set(HAVE_LIBPTHREAD 1) + list(APPEND ZM_BIN_LIBS "${PTHREAD_LIBRARIES}") + find_path(PTHREAD_INCLUDE_DIR pthread.h) + if(PTHREAD_INCLUDE_DIR) + include_directories("${PTHREAD_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${PTHREAD_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE PTHREAD_LIBRARIES PTHREAD_INCLUDE_DIR) + check_include_file("pthread.h" HAVE_PTHREAD_H) + if(NOT HAVE_PTHREAD_H) + message(FATAL_ERROR "ZoneMinder requires pthread headers - check that pthread development packages are installed") + endif() +else() + message(FATAL_ERROR "ZoneMinder requires pthread but it was not found on your system") +endif() -# pcre (using find_library and find_path) -find_library(PCRE_LIBRARIES pcre) -if(PCRE_LIBRARIES) - set(HAVE_LIBPCRE 1) - list(APPEND ZM_BIN_LIBS "${PCRE_LIBRARIES}") - find_path(PCRE_INCLUDE_DIR pcre.h) - if(PCRE_INCLUDE_DIR) - include_directories("${PCRE_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${PCRE_INCLUDE_DIR}") - endif(PCRE_INCLUDE_DIR) - mark_as_advanced(FORCE PCRE_LIBRARIES PCRE_INCLUDE_DIR) - check_include_file("pcre.h" HAVE_PCRE_H) - set(optlibsfound "${optlibsfound} PCRE") -else(PCRE_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} PCRE") -endif(PCRE_LIBRARIES) - -# gcrypt (using find_library and find_path) -find_library(GCRYPT_LIBRARIES gcrypt) -if(GCRYPT_LIBRARIES) - set(HAVE_LIBGCRYPT 1) - list(APPEND ZM_BIN_LIBS "${GCRYPT_LIBRARIES}") - find_path(GCRYPT_INCLUDE_DIR gcrypt.h) - if(GCRYPT_INCLUDE_DIR) - include_directories("${GCRYPT_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${GCRYPT_INCLUDE_DIR}") - endif(GCRYPT_INCLUDE_DIR) - mark_as_advanced(FORCE GCRYPT_LIBRARIES GCRYPT_INCLUDE_DIR) - check_include_file("gcrypt.h" HAVE_GCRYPT_H) - set(optlibsfound "${optlibsfound} GCrypt") -else(GCRYPT_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} GCrypt") -endif(GCRYPT_LIBRARIES) - -# gnutls (using find_library and find_path) -find_library(GNUTLS_LIBRARIES gnutls-openssl) -if(NOT GNUTLS_LIBRARIES) - find_library(GNUTLS_LIBRARIES gnutls) -endif(NOT GNUTLS_LIBRARIES) - -if(GNUTLS_LIBRARIES) - set(HAVE_LIBGNUTLS 1) - list(APPEND ZM_BIN_LIBS "${GNUTLS_LIBRARIES}") - find_path(GNUTLS_INCLUDE_DIR gnutls/gnutls.h) - if(GNUTLS_INCLUDE_DIR) - include_directories("${GNUTLS_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIR}") - endif(GNUTLS_INCLUDE_DIR) - mark_as_advanced(FORCE GNUTLS_LIBRARIES GNUTLS_INCLUDE_DIR) - check_include_file("gnutls/openssl.h" HAVE_GNUTLS_OPENSSL_H) - check_include_file("gnutls/gnutls.h" HAVE_GNUTLS_GNUTLS_H) - set(optlibsfound "${optlibsfound} GnuTLS") -else(GNUTLS_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} GnuTLS") -endif(GNUTLS_LIBRARIES) +# Do not check for cURL if ZM_NO_CURL is on +if(NOT ZM_NO_PRCE) + # pcre (using find_library and find_path) + find_library(PCRE_LIBRARIES pcre) + if(PCRE_LIBRARIES) + set(HAVE_LIBPCRE 1) + list(APPEND ZM_BIN_LIBS "${PCRE_LIBRARIES}") + find_path(PCRE_INCLUDE_DIR pcre.h) + if(PCRE_INCLUDE_DIR) + include_directories("${PCRE_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${PCRE_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE PCRE_LIBRARIES PCRE_INCLUDE_DIR) + check_include_file("pcre.h" HAVE_PCRE_H) + set(optlibsfound "${optlibsfound} PCRE") + else() + set(optlibsnotfound "${optlibsnotfound} PCRE") + endif() +endif() # mysqlclient (using find_library and find_path) find_library(MYSQLCLIENT_LIBRARIES mysqlclient PATH_SUFFIXES mysql) if(MYSQLCLIENT_LIBRARIES) - set(HAVE_LIBMYSQLCLIENT 1) - list(APPEND ZM_BIN_LIBS "${MYSQLCLIENT_LIBRARIES}") - find_path(MYSQLCLIENT_INCLUDE_DIR mysql.h PATH_SUFFIXES mysql) - if(MYSQLCLIENT_INCLUDE_DIR) - include_directories("${MYSQLCLIENT_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${MYSQLCLIENT_INCLUDE_DIR}") - endif(MYSQLCLIENT_INCLUDE_DIR) - mark_as_advanced(FORCE MYSQLCLIENT_LIBRARIES MYSQLCLIENT_INCLUDE_DIR) - check_include_file("mysql.h" HAVE_MYSQL_H) - if(NOT HAVE_MYSQL_H) - message(FATAL_ERROR - "ZoneMinder requires MySQL headers - check that MySQL development packages are installed") - endif(NOT HAVE_MYSQL_H) -else(MYSQLCLIENT_LIBRARIES) - message(FATAL_ERROR - "ZoneMinder requires mysqlclient but it was not found on your system") -endif(MYSQLCLIENT_LIBRARIES) + set(HAVE_LIBMYSQLCLIENT 1) + list(APPEND ZM_BIN_LIBS "${MYSQLCLIENT_LIBRARIES}") + find_path(MYSQLCLIENT_INCLUDE_DIR mysql.h PATH_SUFFIXES mysql) + if(MYSQLCLIENT_INCLUDE_DIR) + include_directories("${MYSQLCLIENT_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${MYSQLCLIENT_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE MYSQLCLIENT_LIBRARIES MYSQLCLIENT_INCLUDE_DIR) + check_include_file("mysql.h" HAVE_MYSQL_H) + if(NOT HAVE_MYSQL_H) + message(FATAL_ERROR "ZoneMinder requires MySQL headers - check that MySQL development packages are installed") + endif() +else() + message(FATAL_ERROR "ZoneMinder requires mysqlclient but it was not found on your system") +endif() -# x264 (using find_library and find_path) -find_library(X264_LIBRARIES x264) -if(X264_LIBRARIES) - set(HAVE_LIBX264 1) - list(APPEND ZM_BIN_LIBS "${X264_LIBRARIES}") - find_path(X264_INCLUDE_DIR x264.h) - if(X264_INCLUDE_DIR) - include_directories("${X264_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${X264_INCLUDE_DIR}") - endif(X264_INCLUDE_DIR) - mark_as_advanced(FORCE X264_LIBRARIES X264_INCLUDE_DIR) - check_include_files("stdint.h;x264.h" HAVE_X264_H) - set(optlibsfound "${optlibsfound} x264") -else(X264_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} x264") -endif(X264_LIBRARIES) +find_package(FFMPEG 55.34.100 REQUIRED + COMPONENTS + avcodec + avformat + avutil + swresample + swscale) -# mp4v2 (using find_library and find_path) -find_library(MP4V2_LIBRARIES mp4v2) -if(MP4V2_LIBRARIES) - set(HAVE_LIBMP4V2 1) - list(APPEND ZM_BIN_LIBS "${MP4V2_LIBRARIES}") - - # mp4v2/mp4v2.h - find_path(MP4V2_INCLUDE_DIR mp4v2/mp4v2.h) - if(MP4V2_INCLUDE_DIR) - include_directories("${MP4V2_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}") - check_include_file("mp4v2/mp4v2.h" HAVE_MP4V2_MP4V2_H) - endif(MP4V2_INCLUDE_DIR) - - # mp4v2.h - find_path(MP4V2_INCLUDE_DIR mp4v2.h) - if(MP4V2_INCLUDE_DIR) - include_directories("${MP4V2_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}") - check_include_file("mp4v2.h" HAVE_MP4V2_H) - endif(MP4V2_INCLUDE_DIR) - - # mp4.h - find_path(MP4V2_INCLUDE_DIR mp4.h) - if(MP4V2_INCLUDE_DIR) - include_directories("${MP4V2_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}") - check_include_file("mp4.h" HAVE_MP4_H) - endif(MP4V2_INCLUDE_DIR) - - mark_as_advanced(FORCE MP4V2_LIBRARIES MP4V2_INCLUDE_DIR) - set(optlibsfound "${optlibsfound} mp4v2") -else(MP4V2_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} mp4v2") -endif(MP4V2_LIBRARIES) +set(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(AVFORMAT_INCLUDE_DIR) - mark_as_advanced(FORCE AVFORMAT_LIBRARIES AVFORMAT_INCLUDE_DIR) - check_include_file("libavformat/avformat.h" HAVE_LIBAVFORMAT_AVFORMAT_H) - set(optlibsfound "${optlibsfound} AVFormat") -else(AVFORMAT_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} AVFormat") -endif(AVFORMAT_LIBRARIES) - -# avcodec (using find_library and find_path) -find_library(AVCODEC_LIBRARIES avcodec) -if(AVCODEC_LIBRARIES) - set(HAVE_LIBAVCODEC 1) - list(APPEND ZM_BIN_LIBS "${AVCODEC_LIBRARIES}") - find_path(AVCODEC_INCLUDE_DIR "libavcodec/avcodec.h" /usr/include/ffmpeg) - if(AVCODEC_INCLUDE_DIR) - include_directories("${AVCODEC_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${AVCODEC_INCLUDE_DIR}") - endif(AVCODEC_INCLUDE_DIR) - mark_as_advanced(FORCE AVCODEC_LIBRARIES AVCODEC_INCLUDE_DIR) - check_include_file("libavcodec/avcodec.h" HAVE_LIBAVCODEC_AVCODEC_H) - set(optlibsfound "${optlibsfound} AVCodec") -else(AVCODEC_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} AVCodec") -endif(AVCODEC_LIBRARIES) - -# avdevice (using find_library and find_path) -find_library(AVDEVICE_LIBRARIES avdevice) -if(AVDEVICE_LIBRARIES) - set(HAVE_LIBAVDEVICE 1) - list(APPEND ZM_BIN_LIBS "${AVDEVICE_LIBRARIES}") - find_path(AVDEVICE_INCLUDE_DIR "libavdevice/avdevice.h" /usr/include/ffmpeg) - if(AVDEVICE_INCLUDE_DIR) - include_directories("${AVDEVICE_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${AVDEVICE_INCLUDE_DIR}") - endif(AVDEVICE_INCLUDE_DIR) - mark_as_advanced(FORCE AVDEVICE_LIBRARIES AVDEVICE_INCLUDE_DIR) - check_include_file("libavdevice/avdevice.h" HAVE_LIBAVDEVICE_AVDEVICE_H) - set(optlibsfound "${optlibsfound} AVDevice") -else(AVDEVICE_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} AVDevice") -endif(AVDEVICE_LIBRARIES) - -# avutil (using find_library and find_path) -find_library(AVUTIL_LIBRARIES avutil) -if(AVUTIL_LIBRARIES) - set(HAVE_LIBAVUTIL 1) - list(APPEND ZM_BIN_LIBS "${AVUTIL_LIBRARIES}") - find_path(AVUTIL_INCLUDE_DIR "libavutil/avutil.h" /usr/include/ffmpeg) - if(AVUTIL_INCLUDE_DIR) - include_directories("${AVUTIL_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${AVUTIL_INCLUDE_DIR}") - endif(AVUTIL_INCLUDE_DIR) - mark_as_advanced(FORCE AVUTIL_LIBRARIES AVUTIL_INCLUDE_DIR) - check_include_file("libavutil/avutil.h" HAVE_LIBAVUTIL_AVUTIL_H) - check_include_file("libavutil/mathematics.h" HAVE_LIBAVUTIL_MATHEMATICS_H) - check_include_file("libavutil/hwcontext.h" HAVE_LIBAVUTIL_HWCONTEXT_H) - set(optlibsfound "${optlibsfound} AVUtil") -else(AVUTIL_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} AVUtil") -endif(AVUTIL_LIBRARIES) - -# swscale (using find_library and find_path) -find_library(SWSCALE_LIBRARIES swscale) -if(SWSCALE_LIBRARIES) - set(HAVE_LIBSWSCALE 1) - list(APPEND ZM_BIN_LIBS "${SWSCALE_LIBRARIES}") - find_path(SWSCALE_INCLUDE_DIR "libswscale/swscale.h" /usr/include/ffmpeg) - if(SWSCALE_INCLUDE_DIR) - include_directories("${SWSCALE_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${SWSCALE_INCLUDE_DIR}") - endif(SWSCALE_INCLUDE_DIR) - mark_as_advanced(FORCE SWSCALE_LIBRARIES SWSCALE_INCLUDE_DIR) - check_include_file("libswscale/swscale.h" HAVE_LIBSWSCALE_SWSCALE_H) - set(optlibsfound "${optlibsfound} SWScale") -else(SWSCALE_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} SWScale") -endif(SWSCALE_LIBRARIES) - -# 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(SWRESAMPLE_INCLUDE_DIR) - mark_as_advanced(FORCE SWRESAMPLE_LIBRARIES SWRESAMPLE_INCLUDE_DIR) - check_include_file("libswresample/swresample.h" HAVE_LIBSWRESAMPLE_SWRESAMPLE_H) - set(optlibsfound "${optlibsfound} SWResample") -else(SWRESAMPLE_LIBRARIES) - 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(AVRESAMPLE_INCLUDE_DIR) - mark_as_advanced(FORCE AVRESAMPLE_LIBRARIES AVRESAMPLE_INCLUDE_DIR) - check_include_file("libavresample/avresample.h" HAVE_LIBAVRESAMPLE_AVRESAMPLE_H) - set(optlibsfound "${optlibsfound} AVResample") - else(AVRESAMPLE_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} AVResample") - endif(AVRESAMPLE_LIBRARIES) - -endif(SWRESAMPLE_LIBRARIES) # Find the path to the ffmpeg executable -find_program(FFMPEG_EXECUTABLE - NAMES ffmpeg avconv - PATH_SUFFIXES ffmpeg) +find_program(FFMPEG_EXECUTABLE + NAMES ffmpeg avconv + PATH_SUFFIXES ffmpeg) if(FFMPEG_EXECUTABLE) - set(PATH_FFMPEG "${FFMPEG_EXECUTABLE}") - set(OPT_FFMPEG "yes") - mark_as_advanced(FFMPEG_EXECUTABLE) -endif(FFMPEG_EXECUTABLE) + set(PATH_FFMPEG "${FFMPEG_EXECUTABLE}") + set(OPT_FFMPEG "yes") + mark_as_advanced(FFMPEG_EXECUTABLE) +endif() # Do not check for libvlc if ZM_NO_LIBVLC is on if(NOT ZM_NO_LIBVLC) - # libvlc (using find_library and find_path) - find_library(LIBVLC_LIBRARIES vlc) - if(LIBVLC_LIBRARIES) - set(HAVE_LIBVLC 1) - list(APPEND ZM_BIN_LIBS "${LIBVLC_LIBRARIES}") - find_path(LIBVLC_INCLUDE_DIR "vlc/vlc.h") - if(LIBVLC_INCLUDE_DIR) - include_directories("${LIBVLC_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${LIBVLC_INCLUDE_DIR}") - endif(LIBVLC_INCLUDE_DIR) - mark_as_advanced(FORCE LIBVLC_LIBRARIES LIBVLC_INCLUDE_DIR) - check_include_file("vlc/vlc.h" HAVE_VLC_VLC_H) - set(optlibsfound "${optlibsfound} libVLC") - else(LIBVLC_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} libVLC") - endif(LIBVLC_LIBRARIES) -endif(NOT ZM_NO_LIBVLC) + # libvlc (using find_library and find_path) + find_library(LIBVLC_LIBRARIES vlc) + if(LIBVLC_LIBRARIES) + set(HAVE_LIBVLC 1) + #list(APPEND ZM_BIN_LIBS "${LIBVLC_LIBRARIES}") + find_path(LIBVLC_INCLUDE_DIR "vlc/vlc.h") + if(LIBVLC_INCLUDE_DIR) + include_directories("${LIBVLC_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${LIBVLC_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE LIBVLC_LIBRARIES LIBVLC_INCLUDE_DIR) + check_include_file("vlc/vlc.h" HAVE_VLC_VLC_H) + set(optlibsfound "${optlibsfound} libVLC") + else() + set(optlibsnotfound "${optlibsnotfound} libVLC") + endif() +endif() + +if(NOT ZM_NO_LIBVNC) + # libvncclient (using find_library and find_path) + find_library(LIBVNC_LIBRARIES vncclient) + if(LIBVNC_LIBRARIES) + set(HAVE_LIBVNC 1) + #list(APPEND ZM_BIN_LIBS "${LIBVNC_LIBRARIES}") + find_path(LIBVNC_INCLUDE_DIR "rfb/rfb.h") + if(LIBVNC_INCLUDE_DIR) + include_directories("${LIBVNC_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${LIBVNC_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE LIBVNC_LIBRARIES LIBVNC_INCLUDE_DIR) + check_include_file("rfb/rfb.h" HAVE_RFB_RFB_H) + set(optlibsfound "${optlibsfound} libVNC") + else() + set(optlibsnotfound "${optlibsnotfound} libVNC") + endif() +endif() #find_package(Boost 1.36.0) #if(Boost_FOUND) - #include_directories(${Boost_INCLUDE_DIRS}) - ##set(CMAKE_REQUIRED_INCLUDES "${Boost_INCLUDE_DIRS}") - #list(APPEND ZM_BIN_LIBS "${Boost_LIBRARIES}") +#include_directories(${Boost_INCLUDE_DIRS}) +##set(CMAKE_REQUIRED_INCLUDES "${Boost_INCLUDE_DIRS}") +#list(APPEND ZM_BIN_LIBS "${Boost_LIBRARIES}") #endif() + + +find_package(GSOAP 2.0.0) +if (GSOAP_FOUND) + set(optlibsfound "${optlibsfound} gsoap") + add_compile_definitions(WITH_GSOAP) +else() + set(optlibsnotfound "${optlibsnotfound} gsoap") +endif() + +if(NOT ZM_NO_RTSPSERVER) + set(HAVE_RTSP_SERVER 1) +else() + set(HAVE_RTSP_SERVER 0) +endif() + # # *** END OF LIBRARY CHECKS *** -# Check for gnutls or crypto -if((NOT HAVE_LIBCRYPTO) AND (NOT HAVE_LIBGNUTLS)) - message(FATAL_ERROR - "ZoneMinder requires crypto or gnutls but none were found on your system") -endif((NOT HAVE_LIBCRYPTO) AND (NOT HAVE_LIBGNUTLS)) +# 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() + +find_package(V4L2) +if(TARGET V4L2::videodev2) + set(ZM_HAS_V4L2 1) +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 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(HAVE_LINUX_VIDEODEV_H OR HAVE_LIBV4L1_VIDEODEV_H) -if(HAVE_LINUX_VIDEODEV2_H) - set(ZM_HAS_V4L 1) - set(ZM_HAS_V4L2 1) -endif(HAVE_LINUX_VIDEODEV2_H) -if((NOT HAVE_LINUX_VIDEODEV_H) - AND (NOT HAVE_LIBV4L1_VIDEODEV_H) - AND (NOT HAVE_LINUX_VIDEODEV2_H)) - message(AUTHOR_WARNING - "Video 4 Linux headers weren't found - Analog and USB camera support will not be available") -endif((NOT HAVE_LINUX_VIDEODEV_H) - AND (NOT HAVE_LIBV4L1_VIDEODEV_H) - AND (NOT HAVE_LINUX_VIDEODEV2_H)) # Check for PCRE and enable ZM_PCRE accordingly set(ZM_PCRE 0) if(HAVE_LIBPCRE AND HAVE_PCRE_H) - set(ZM_PCRE 1) -endif(HAVE_LIBPCRE AND HAVE_PCRE_H) + set(ZM_PCRE 1) +endif() + # Check for mmap and enable in all components set(ZM_MEM_MAPPED 0) set(ENABLE_MMAP no) if(NOT ZM_NO_MMAP) - set(ZM_MEM_MAPPED 1) - set(ENABLE_MMAP yes) - set(ZM_MMAP_PERLPACKAGE "Sys::Mmap") -endif(NOT ZM_NO_MMAP) + set(ZM_MEM_MAPPED 1) + set(ENABLE_MMAP yes) + set(ZM_MMAP_PERLPACKAGE "Sys::Mmap") +endif() # Check for the ONVIF flag and enable ZM_HAS_ONVIF accordingly set(ZM_HAS_ONVIF 0) if(ZM_ONVIF) - set(ZM_HAS_ONVIF 1) -endif(ZM_ONVIF) - -# Check for authentication functions -if(HAVE_OPENSSL_MD5_H) - set(CMAKE_REQUIRED_LIBRARIES "${OPENSSL_LIBRARIES}") - set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") - check_prototype_definition( - MD5 - "unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md)" "NULL" "openssl/md5.h" - HAVE_MD5_OPENSSL) -endif(HAVE_OPENSSL_MD5_H) -if(HAVE_GNUTLS_OPENSSL_H) - set(CMAKE_REQUIRED_LIBRARIES "${GNUTLS_LIBRARIES}") - set(CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIR}") - check_prototype_definition( - MD5 - "unsigned char *MD5 (const unsigned char *buf, unsigned long len, unsigned char *md)" "NULL" "gnutls/openssl.h" - HAVE_MD5_GNUTLS) -endif(HAVE_GNUTLS_OPENSSL_H) -if(HAVE_GNUTLS_GNUTLS_H) - set(CMAKE_REQUIRED_LIBRARIES "${GNUTLS_LIBRARIES}") - set(CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIR}") - check_prototype_definition( - gnutls_fingerprint - "int gnutls_fingerprint (gnutls_digest_algorithm_t algo, const gnutls_datum_t * data, void *result, size_t * result_size)" "0" "stdlib.h;gnutls/gnutls.h" - HAVE_DECL_GNUTLS_FINGERPRINT) -endif(HAVE_GNUTLS_GNUTLS_H) -if(HAVE_MD5_OPENSSL OR HAVE_MD5_GNUTLS) - set(HAVE_DECL_MD5 1) -else(HAVE_MD5_OPENSSL OR HAVE_MD5_GNUTLS) - message(AUTHOR_WARNING - "ZoneMinder requires a working MD5 function for hashed authenication but - none were found - hashed authenication will not be available") -endif(HAVE_MD5_OPENSSL OR HAVE_MD5_GNUTLS) -# Dirty fix for zm_user only using openssl's md5 if gnutls and gcrypt are not available. -# This needs to be fixed in zm_user.[h,cpp] but such fix will also require changes to configure.ac -if(HAVE_LIBCRYPTO AND HAVE_OPENSSL_MD5_H AND HAVE_MD5_OPENSSL) - set(HAVE_GCRYPT_H 0) - set(HAVE_GNUTLS_OPENSSL_H 0) -endif(HAVE_LIBCRYPTO AND HAVE_OPENSSL_MD5_H AND HAVE_MD5_OPENSSL) + set(ZM_HAS_ONVIF 1) +endif() # Check for Perl find_package(Perl) if(NOT PERL_FOUND) - message(FATAL_ERROR - "ZoneMinder requires Perl 5.6.0 or newer but it was not found on your system") -endif(NOT PERL_FOUND) + message(FATAL_ERROR "ZoneMinder requires Perl 5.6.0 or newer but it was not found on your system") +endif() # Checking for perl modules requires FindPerlModules.cmake # Check all required modules at once # TODO: Add checking for the optional modules find_package( - PerlModules COMPONENTS Sys::Syslog DBI DBD::mysql - Getopt::Long Time::HiRes Date::Manip LWP::UserAgent - ExtUtils::MakeMaker ${ZM_MMAP_PERLPACKAGE}) + PerlModules COMPONENTS Sys::Syslog DBI DBD::mysql + Getopt::Long Time::HiRes Date::Manip LWP::UserAgent + ExtUtils::MakeMaker ${ZM_MMAP_PERLPACKAGE}) if(NOT PERLMODULES_FOUND) - message(FATAL_ERROR - "Not all required perl modules were found on your system") -endif(NOT PERLMODULES_FOUND) + message(WARNING "Not all required perl modules were found on your system") +endif() # Attempt to check which user apache (or other web server) runs on by # searching for a user beginning with apache or www and then cutting the user # from the first matching user line if(ZM_WEB_USER STREQUAL "") - # Check for a user matching ^apache and cut the username from the - # userline in the first match - file(STRINGS "/etc/passwd" userline_apache REGEX "^apache") - file(STRINGS "/etc/passwd" userline_www REGEX "^www") - if(NOT (userline_apache STREQUAL "")) - execute_process( - COMMAND echo ${userline_apache} - COMMAND cut -d: -f1 OUTPUT_VARIABLE ZM_WEB_USER - OUTPUT_STRIP_TRAILING_WHITESPACE) - elseif(NOT (userline_www STREQUAL "")) - execute_process( - COMMAND echo ${userline_www} - COMMAND cut -d: -f1 OUTPUT_VARIABLE ZM_WEB_USER - OUTPUT_STRIP_TRAILING_WHITESPACE) - endif(NOT (userline_apache STREQUAL "")) - message(STATUS "Detected web server user: ${ZM_WEB_USER}") -endif(ZM_WEB_USER STREQUAL "") + # Check for a user matching ^apache and cut the username from the + # userline in the first match + file(STRINGS "/etc/passwd" userline_apache REGEX "^apache") + file(STRINGS "/etc/passwd" userline_www REGEX "^www") + if(NOT (userline_apache STREQUAL "")) + execute_process( + COMMAND echo ${userline_apache} + COMMAND cut -d: -f1 OUTPUT_VARIABLE ZM_WEB_USER + OUTPUT_STRIP_TRAILING_WHITESPACE) + elseif(NOT (userline_www STREQUAL "")) + execute_process( + COMMAND echo ${userline_www} + COMMAND cut -d: -f1 OUTPUT_VARIABLE ZM_WEB_USER + OUTPUT_STRIP_TRAILING_WHITESPACE) + endif() + message(STATUS "Detected web server user: ${ZM_WEB_USER}") +endif() # Check if webgroup contains anything. If not, use the web user as the web group if(NOT ZM_WEB_GROUP) - set(ZM_WEB_GROUP ${ZM_WEB_USER}) -endif(NOT ZM_WEB_GROUP) + set(ZM_WEB_GROUP ${ZM_WEB_USER}) +endif() message(STATUS "Using web user: ${ZM_WEB_USER}") message(STATUS "Using web group: ${ZM_WEB_GROUP}") if(WITH_SYSTEMD) - # Check for polkit - find_package(Polkit) - if(NOT POLKIT_FOUND) - message(FATAL_ERROR - "Running ZoneMinder requires polkit. Building ZoneMinder requires the polkit development package.") - endif(NOT POLKIT_FOUND) -endif(WITH_SYSTEMD) + # Check for polkit + find_package(Polkit) + if(NOT POLKIT_FOUND) + message(WARNING "Running ZoneMinder requires polkit. Building ZoneMinder requires the polkit development package.") + endif() +endif() # Find the path to an arp compatible executable if(ZM_PATH_ARP STREQUAL "") - find_program(ARP_EXECUTABLE arp) + find_program(ARP_EXECUTABLE arp) + if(ARP_EXECUTABLE) + set(ZM_PATH_ARP "${ARP_EXECUTABLE}") + mark_as_advanced(ARP_EXECUTABLE) + else() + find_program(ARP_EXECUTABLE ip) if(ARP_EXECUTABLE) - set(ZM_PATH_ARP "${ARP_EXECUTABLE}") - mark_as_advanced(ARP_EXECUTABLE) - else(ARP_EXECUTABLE) - find_program(ARP_EXECUTABLE ip) - if(ARP_EXECUTABLE) - set(ZM_PATH_ARP "${ARP_EXECUTABLE} neigh") - mark_as_advanced(ARP_EXECUTABLE) - endif(ARP_EXECUTABLE) - endif(ARP_EXECUTABLE) - if(ARP_EXECUTABLE-NOTFOUND) - message(WARNING "Unable to find a compatible arp binary. Monitor probe will not function." ) - endif(ARP_EXECUTABLE-NOTFOUND) -endif(ZM_PATH_ARP STREQUAL "") + set(ZM_PATH_ARP "${ARP_EXECUTABLE} neigh") + mark_as_advanced(ARP_EXECUTABLE) + endif() + endif() + if(ARP_EXECUTABLE-NOTFOUND) + message(WARNING "Unable to find a compatible arp binary. Monitor probe will not function.") + endif() +endif() + +# Find the path to an arp-scan compatible executable +if(ZM_PATH_ARP_SCAN STREQUAL "") + find_program(ARP_SCAN_EXECUTABLE arp-scan) + if(ARP_SCAN_EXECUTABLE) + set(ZM_PATH_ARP_SCAN "${ARP_SCAN_EXECUTABLE}") + mark_as_advanced(ARP_SCAN_EXECUTABLE) + endif() + if(ARP_SCAN_EXECUTABLE-NOTFOUND) + message(WARNING "Unable to find a compatible arp-scan binary. Monitor probe will be less powerful.") + endif() +endif() # Some variables that zm expects set(ZM_PID "${ZM_RUNDIR}/zm.pid") @@ -850,15 +671,15 @@ set(WEB_USER "${ZM_WEB_USER}") set(WEB_GROUP "${ZM_WEB_GROUP}") set(ZM_DB_TYPE "mysql") if(ZM_PERL_SEARCH_PATH) - set(EXTRA_PERL_LIB "use lib '${ZM_PERL_SEARCH_PATH}'; # Include custom perl install path") -else(ZM_PERL_SEARCH_PATH) - set(EXTRA_PERL_LIB "# Include from system perl paths only") -endif(ZM_PERL_SEARCH_PATH) + set(EXTRA_PERL_LIB "use lib '${ZM_PERL_SEARCH_PATH}'; # Include custom perl install path") +else() + set(EXTRA_PERL_LIB "# Include from system perl paths only") +endif() # If this is an out-of-source build, copy the files we need to the binary directory if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/conf.d" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE) -endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/conf.d" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE) +endif() # Generate files from the .in files configure_file(zm.conf.in "${CMAKE_CURRENT_BINARY_DIR}/zm.conf" @ONLY) @@ -869,48 +690,49 @@ configure_file(zmlinkcontent.sh.in "${CMAKE_CURRENT_BINARY_DIR}/zmlinkcontent.sh # Create a target for man pages include(Pod2Man) -ADD_MANPAGE_TARGET() # Process subdirectories - -# build a bcrypt static library -set(BUILD_SHARED_LIBS_SAVED "${BUILD_SHARED_LIBS}") -set(BUILD_SHARED_LIBS OFF) -add_subdirectory(src/libbcrypt EXCLUDE_FROM_ALL) -set(BUILD_SHARED_LIBS "${BUILD_SHARED_LIBS_SAVED}") - +add_subdirectory(dep) add_subdirectory(src) add_subdirectory(scripts) add_subdirectory(db) +add_subdirectory(fonts) add_subdirectory(web) add_subdirectory(misc) +add_subdirectory(onvif) -# Enable ONVIF support -if(ZM_ONVIF) - add_subdirectory(onvif) -endif(ZM_ONVIF) +if(BUILD_TEST_SUITE) + message("Building unit tests: Yes") + find_package(Catch2 REQUIRED) + + include(CTest) + add_subdirectory(tests) +else() + message("Building unit tests: No (default)") +endif() # Process distro subdirectories if((ZM_TARGET_DISTRO MATCHES "^el") OR (ZM_TARGET_DISTRO MATCHES "^fc")) - add_subdirectory(distros/redhat) -elseif(ZM_TARGET_DISTRO STREQUAL "OS13") - add_subdirectory(distros/opensuse) -endif((ZM_TARGET_DISTRO MATCHES "^el") OR (ZM_TARGET_DISTRO MATCHES "^fc")) + add_subdirectory(distros/redhat) +elseif() + add_subdirectory(distros/opensuse) +endif() # Print optional libraries detection status message(STATUS "Optional libraries found:${optlibsfound}") 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 ./zmconfgen.pl RESULT_VARIABLE zmconfgen_result) -if(zmconfgen_result EQUAL 0) - message(STATUS - "ZoneMinder configuration generator completed successfully") -else(zmconfgen_result EQUAL 0) - message(FATAL_ERROR - "ZoneMinder configuration generator failed. Exit code: ${zmconfgen_result}") -endif(zmconfgen_result EQUAL 0) +execute_process(COMMAND perl ${CMAKE_CURRENT_BINARY_DIR}/zmconfgen.pl RESULT_VARIABLE ZMCONFGEN_RESULT) +if(ZMCONFGEN_RESULT EQUAL 0) + message(STATUS "ZoneMinder configuration generator completed successfully") +else() + message(FATAL_ERROR "ZoneMinder configuration generator failed. Exit code: ${zmconfgen_result}") +endif() # Install zm.conf install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm.conf" DESTINATION "${ZM_CONFIG_DIR}") @@ -918,17 +740,17 @@ install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/conf.d/" DESTINATION "${ZM_CONFIG # Uninstall target configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake" - IMMEDIATE @ONLY) + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake" + IMMEDIATE @ONLY) add_custom_target(uninstall - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake) + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake) # Configure CCache if available find_program(CCACHE_FOUND ccache) if(CCACHE_FOUND) - set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) -endif(CCACHE_FOUND) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) +endif() install(DIRECTORY icons DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/") diff --git a/README.md b/README.md index b9d215a40..bcb53ba8a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ ZoneMinder ========== -[![Build Status](https://travis-ci.org/ZoneMinder/zoneminder.png)](https://travis-ci.org/ZoneMinder/zoneminder) [![Bounty Source](https://api.bountysource.com/badge/team?team_id=204&style=bounties_received)](https://www.bountysource.com/teams/zoneminder/issues?utm_source=ZoneMinder&utm_medium=shield&utm_campaign=bounties_received) [![Join Slack](https://github.com/ozonesecurity/ozonebase/blob/master/img/slacksm.png?raw=true)](https://join.slack.com/t/zoneminder-chat/shared_invite/enQtNTU0NDkxMDM5NDQwLTdhZmQ5Y2M2NWQyN2JkYTBiN2ZkMzIzZGQ0MDliMTRmM2FjZWRlYzUwYTQ2MjMwMTVjMzQ1NjYxOTdmMjE2MTE) -[![IRC Network](https://img.shields.io/badge/irc-%23zoneminder-blue.svg "IRC Freenode")](https://webchat.freenode.net/?channels=zoneminder) All documentation for ZoneMinder is now online at https://zoneminder.readthedocs.org @@ -22,26 +20,27 @@ https://github.com/ZoneMinder/zmdockerfiles ## Installation Methods +### Install from a Package Repository + +This is the recommended method to install ZoneMinder onto your system. ZoneMinder packages are maintained for the following distros: + +- Ubuntu via [Isaac Connor's PPA](https://launchpad.net/~iconnor) +- 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](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) + +If a repository that hosts ZoneMinder packages is not available for your distro, then you are encouraged to build your own package, rather than build from source. While each distro is different in ways that set it apart from all the others, they are often similar enough to allow you to adapt another distro's package building instructions to your own. + ### Building from Source is Discouraged Historically, installing ZoneMinder onto your system required building from source code by issuing the traditional configure, make, make install commands. To get ZoneMinder to build, all of its dependencies had to be determined and installed beforehand. Init and logrotate scripts had to be manually copied into place following the build. Optional packages such as jscalendar and Cambozola had to be manually installed. Uninstalls could leave stale files around, which could cause problems during an upgrade. Speaking of upgrades, when it comes time to upgrade all these manual steps must be repeated again. Better methods exist today that do much of this for you. The current development team, along with other volunteers, have taken great strides in providing the resources necessary to avoid building from source. -### Install from a Package Repository - -This is the recommended method to install ZoneMinder onto your system. ZoneMinder packages are maintained for the following distros: - -- Ubuntu via [Iconnor's PPA](https://launchpad.net/~iconnor) -- 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) -- Mageia from their default repository -- Arch via the [AUR](https://aur.archlinux.org/packages/zoneminder/) -- Gentoo from their [default repository](https://packages.gentoo.org/packages/www-misc/zoneminder) - -If a repository that hosts ZoneMinder packages is not available for your distro, then you are encouraged to build your own package, rather than build from source. While each distro is different in ways that set it apart from all the others, they are often similar enough to allow you to adapt another distro's package building instructions to your own. ### Building a ZoneMinder Package ### @@ -69,18 +68,19 @@ Docker is a system to run applications inside isolated containers. ZoneMinder, a Dockerfile contained in this repository. However, there is still work needed to ensure that the main ZM features work properly and are documented. -## Contribution Model and Development +## Contribution Model and Development * Source hosted at [GitHub](https://github.com/ZoneMinder/ZoneMinder/) -* Report issues/questions/feature requests on [GitHub Issues](https://github.com/ZoneMinder/ZoneMinder/issues) +* Report issues at [GitHub Issues](https://github.com/ZoneMinder/ZoneMinder/issues) +* Questions/feature requests in [Slack](https://zoneminder-chat.slack.com/) or [forums](https://forums.zoneminder.com) Pull requests are very welcome! If you would like to contribute, please follow -the following steps. +the following steps. While step 3 is optional, it is preferred. 1. Fork the repo 2. Open an issue at our [GitHub Issues Tracker](https://github.com/ZoneMinder/ZoneMinder/issues). - Describe the bug that you've found, or the feature which you're asking for. - Jot down the issue number (e.g. 456) + Follow the issue template to describe the bug or security issue you found. Please note feature + requests or questions should be posted in our user forum or Slack channel. 3. Create your feature branch (`git checkout -b 456-my-new-feature`) 4. Commit your changes (`git commit -am 'Added some feature'`) It is preferred that you 'commit early and often' instead of bunching all diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..c3b4c7211 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,18 @@ +# Security Policy + +## Supported Versions + +Time and computers move on. We do not have the resources to support every ancient version of everything +(unless you'd like to pay us to do so.) We ONLY support the latest stable release and development releases. + +| Version | Supported | +| ------- | ------------------ | +| 1.34.x | :white_check_mark: | +| 1.35.x | :white_check_mark: | +| < 1.34.x | :x: | + +## Reporting a Vulnerability + +Since sometimes security vulnerabilities can be sensitive, you can just email me at isaac@zoneminder.com. +If it's not such a big deal, by all means, create an issue here on github + diff --git a/cmake/Modules/CheckPlatform.cmake b/cmake/Modules/CheckPlatform.cmake new file mode 100644 index 000000000..6d88efe32 --- /dev/null +++ b/cmake/Modules/CheckPlatform.cmake @@ -0,0 +1,44 @@ +set(HOST_OS "") +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(HOST_OS "linux") +endif() +if(${CMAKE_SYSTEM_NAME} MATCHES ".*(SunOS|Solaris).*") + set(HOST_OS "solaris") + set(SOLARIS 1) +endif() +if(${CMAKE_SYSTEM_NAME} MATCHES ".*BSD.*") + set(HOST_OS "BSD") + set(BSD 1) +endif() +if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + set(HOST_OS "darwin") +endif() +if(NOT HOST_OS) + message(FATAL_ERROR + "ZoneMinder was unable to deterimine the host OS. Please report this. + Value of CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}") +endif() + +if(CMAKE_SYSTEM_NAME MATCHES "Linux") + string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" ZM_SYSTEM_PROC) + if((ZM_SYSTEM_PROC STREQUAL "") OR (ZM_SYSTEM_PROC STREQUAL "unknown")) + execute_process(COMMAND uname -m OUTPUT_VARIABLE ZM_SYSTEM_PROC ERROR_VARIABLE ZM_SYSTEM_PROC_ERR) + + # maybe make the following error checks fatal + if(ZM_SYSTEM_PROC_ERR) + message(WARNING "\nAn error occurred while attempting to determine the system processor:\n${ZM_SYSTEM_PROC_ERR}") + endif() + if(NOT ZM_SYSTEM_PROC) + message(WARNING "\nUnable to determine the system processor. This may cause a build failure.\n") + endif() + endif() +endif() + +message(STATUS "Detected compiler: ${CMAKE_C_COMPILER}") +if(CMAKE_C_COMPILER MATCHES "gcc" OR CMAKE_C_COMPILER_ID STREQUAL "GNU") + include(${CMAKE_SOURCE_DIR}/cmake/compiler/gcc/settings.cmake) +elseif(CMAKE_C_COMPILER MATCHES "clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang") + include(${CMAKE_SOURCE_DIR}/cmake/compiler/clang/settings.cmake) +else() + message(FATAL_ERROR "No supported compiler found") +endif() diff --git a/cmake/Modules/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/ConfigureBaseTargets.cmake b/cmake/Modules/ConfigureBaseTargets.cmake new file mode 100644 index 000000000..ed86d35fc --- /dev/null +++ b/cmake/Modules/ConfigureBaseTargets.cmake @@ -0,0 +1,50 @@ +add_library(zm-compile-option-interface INTERFACE) + +# Use -std=c++11 instead of -std=gnu++11 +set(CMAKE_CXX_EXTENSIONS OFF) + +add_library(zm-feature-interface INTERFACE) + +# The cxx_std_* feature flags were only introduced in CMake 3.8 +# Use to old way to specify the required standard level for older CMake versions. +# Remove this once we raise the required CMake version. +if(${CMAKE_VERSION} VERSION_LESS 3.8.0) + set(CMAKE_CXX_STANDARD 11) +else() + target_compile_features(zm-feature-interface + INTERFACE + cxx_std_11) +endif() + +# Interface to set warning levels on targets. +# It gets populated in the compiler specific script. +add_library(zm-warning-interface INTERFACE) + +# Interface which disables all warnings on the target. +add_library(zm-no-warning-interface INTERFACE) +target_compile_options(zm-no-warning-interface + INTERFACE + -w) + +# An interface used by all other interfaces. +add_library(zm-default-interface INTERFACE) +target_link_libraries(zm-default-interface + INTERFACE + zm-compile-option-interface + zm-feature-interface) + +# An interface which provides the flags and definitions +# used by the non-dependency targets. +add_library(zm-core-interface INTERFACE) +target_link_libraries(zm-core-interface + INTERFACE + zm-default-interface + zm-warning-interface) + +# An interface which provides the flags and definitions +# used by the external dependency targets. +add_library(zm-dependency-interface INTERFACE) +target_link_libraries(zm-dependency-interface + INTERFACE + zm-default-interface + zm-no-warning-interface) diff --git a/cmake/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/FindFmt.cmake b/cmake/Modules/FindFmt.cmake new file mode 100644 index 000000000..b426d8c77 --- /dev/null +++ b/cmake/Modules/FindFmt.cmake @@ -0,0 +1,100 @@ +# FindFmt +# ------- +# Finds the Fmt library +# +# This will define the following variables:: +# +# FMT_FOUND - system has Fmt +# FMT_INCLUDE_DIRS - the Fmt include directory +# FMT_LIBRARIES - the Fmt libraries +# +# and the following imported targets:: +# +# Fmt::Fmt - The Fmt library + +if(ENABLE_INTERNAL_FMT) + include(ExternalProject) + file(STRINGS ${CMAKE_SOURCE_DIR}/tools/depends/target/libfmt/Makefile VER REGEX "^[ ]*VERSION[ ]*=.+$") + string(REGEX REPLACE "^[ ]*VERSION[ ]*=[ ]*" "" FMT_VERSION "${VER}") + + # allow user to override the download URL with a local tarball + # needed for offline build envs + if(FMT_URL) + get_filename_component(FMT_URL "${FMT_URL}" ABSOLUTE) + else() + set(FMT_URL http://mirrors.kodi.tv/build-deps/sources/fmt-${FMT_VERSION}.tar.gz) + endif() + if(VERBOSE) + message(STATUS "FMT_URL: ${FMT_URL}") + endif() + + if(APPLE) + set(EXTRA_ARGS "-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}") + endif() + + set(FMT_LIBRARY ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/lib/libfmt.a) + set(FMT_INCLUDE_DIR ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/include) + externalproject_add(fmt + URL ${FMT_URL} + DOWNLOAD_DIR ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/download + PREFIX ${CORE_BUILD_DIR}/fmt + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR} + -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS} + -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} + -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} + -DCMAKE_INSTALL_LIBDIR=lib + -DFMT_DOC=OFF + -DFMT_TEST=OFF + "${EXTRA_ARGS}" + BUILD_BYPRODUCTS ${FMT_LIBRARY}) + set_target_properties(fmt PROPERTIES FOLDER "External Projects") + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Fmt + REQUIRED_VARS FMT_LIBRARY FMT_INCLUDE_DIR + VERSION_VAR FMT_VERSION) + + set(FMT_LIBRARIES ${FMT_LIBRARY}) + set(FMT_INCLUDE_DIRS ${FMT_INCLUDE_DIR}) + +else() + +find_package(FMT 6.1.2 CONFIG REQUIRED QUIET) + +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FMT libfmt QUIET) + if(PC_FMT_VERSION AND NOT FMT_VERSION) + set(FMT_VERSION ${PC_FMT_VERSION}) + endif() +endif() + +find_path(FMT_INCLUDE_DIR NAMES fmt/format.h + PATHS ${PC_FMT_INCLUDEDIR}) + +find_library(FMT_LIBRARY_RELEASE NAMES fmt + PATHS ${PC_FMT_LIBDIR}) +find_library(FMT_LIBRARY_DEBUG NAMES fmtd + PATHS ${PC_FMT_LIBDIR}) + +include(SelectLibraryConfigurations) +select_library_configurations(FMT) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Fmt + REQUIRED_VARS FMT_LIBRARY FMT_INCLUDE_DIR FMT_VERSION + VERSION_VAR FMT_VERSION) + +if(FMT_FOUND) + set(FMT_LIBRARIES ${FMT_LIBRARY}) + set(FMT_INCLUDE_DIRS ${FMT_INCLUDE_DIR}) + + if(NOT TARGET fmt) + add_library(fmt UNKNOWN IMPORTED) + set_target_properties(fmt PROPERTIES + IMPORTED_LOCATION "${FMT_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${FMT_INCLUDE_DIR}") + endif() +endif() + +endif() +mark_as_advanced(FMT_INCLUDE_DIR FMT_LIBRARY) diff --git a/cmake/Modules/FindGSOAP.cmake b/cmake/Modules/FindGSOAP.cmake new file mode 100644 index 000000000..c7f181bec --- /dev/null +++ b/cmake/Modules/FindGSOAP.cmake @@ -0,0 +1,113 @@ +# +# This module detects if gsoap is installed and determines where the +# include files and libraries are. +# +# This code sets the following variables: +# +# GSOAP_IMPORT_DIR = full path to the gsoap import directory +# GSOAP_LIBRARIES = full path to the gsoap libraries +# GSOAP_SSL_LIBRARIES = full path to the gsoap ssl libraries +# GSOAP_INCLUDE_DIR = include dir to be used when using the gsoap library +# GSOAP_PLUGIN_DIR = gsoap plugins directory +# GSOAP_WSDL2H = wsdl2h binary +# GSOAP_SOAPCPP2 = soapcpp2 binary +# GSOAP_FOUND = set to true if gsoap was found successfully +# +# GSOAP_ROOT +# setting this enables search for gsoap libraries / headers in this location + +# ----------------------------------------------------- +# GSOAP Import Directories +# ----------------------------------------------------- +find_path(GSOAP_IMPORT_DIR + NAMES wsa.h + PATHS ${GSOAP_ROOT}/import ${GSOAP_ROOT}/share/gsoap/import +) + +# ----------------------------------------------------- +# GSOAP Libraries +# ----------------------------------------------------- +find_library(GSOAP_CXX_LIBRARIES + NAMES gsoap++ + HINTS ${GSOAP_ROOT}/lib ${GSOAP_ROOT}/lib64 + ${GSOAP_ROOT}/lib32 + DOC "The main gsoap library" +) +find_library(GSOAP_SSL_CXX_LIBRARIES + NAMES gsoapssl++ + HINTS ${GSOAP_ROOT}/lib ${GSOAP_ROOT}/lib64 + ${GSOAP_ROOT}/lib32 + DOC "The ssl gsoap library" +) + + +# ----------------------------------------------------- +# GSOAP Include Directories +# ----------------------------------------------------- +find_path(GSOAP_INCLUDE_DIR + NAMES stdsoap2.h + HINTS ${GSOAP_ROOT} ${GSOAP_ROOT}/include ${GSOAP_ROOT}/include/* + DOC "The gsoap include directory" +) + +# ----------------------------------------------------- +# GSOAP plugin Directories +# ----------------------------------------------------- +find_path(GSOAP_PLUGIN_DIR + NAMES wsseapi.c + HINTS ${GSOAP_ROOT} /usr/share/gsoap/plugin + DOC "The gsoap plugin directory" +) + +# ----------------------------------------------------- +# GSOAP Binaries +# ---------------------------------------------------- +if(NOT GSOAP_TOOL_DIR) + set(GSOAP_TOOL_DIR GSOAP_ROOT) +endif() + +find_program(GSOAP_WSDL2H + NAMES wsdl2h + HINTS ${GSOAP_TOOL_DIR}/bin + DOC "The gsoap bin directory" +) +find_program(GSOAP_SOAPCPP2 + NAMES soapcpp2 + HINTS ${GSOAP_TOOL_DIR}/bin + DOC "The gsoap bin directory" +) +# ----------------------------------------------------- +# GSOAP version +# try to determine the flagfor the 2.7.6 compatiblity, break with 2.7.13 and re-break with 2.7.16 +# ---------------------------------------------------- +if(GSOAP_SOAPCPP2) + execute_process(COMMAND ${GSOAP_SOAPCPP2} "-V" OUTPUT_VARIABLE GSOAP_STRING_VERSION ERROR_VARIABLE GSOAP_STRING_VERSION ) + string(REGEX MATCH "[0-9]*\\.[0-9]*\\.[0-9]*" GSOAP_VERSION ${GSOAP_STRING_VERSION}) +endif() +# ----------------------------------------------------- +# GSOAP_276_COMPAT_FLAGS and GSOAPVERSION +# try to determine the flagfor the 2.7.6 compatiblity, break with 2.7.13 and re-break with 2.7.16 +# ---------------------------------------------------- +if( "${GSOAP_VERSION}" VERSION_LESS "2.7.6") + set(GSOAP_276_COMPAT_FLAGS "") +elseif ( "${GSOAP_VERSION}" VERSION_LESS "2.7.14") + set(GSOAP_276_COMPAT_FLAGS "-z") +else ( "${GSOAP_VERSION}" VERSION_LESS "2.7.14") + set(GSOAP_276_COMPAT_FLAGS "-z1 -z2") +endif ( "${GSOAP_VERSION}" VERSION_LESS "2.7.6") + +# ----------------------------------------------------- +# handle the QUIETLY and REQUIRED arguments and set GSOAP_FOUND to TRUE if +# all listed variables are TRUE +# ----------------------------------------------------- +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GSOAP DEFAULT_MSG GSOAP_CXX_LIBRARIES + GSOAP_INCLUDE_DIR GSOAP_WSDL2H GSOAP_SOAPCPP2) +mark_as_advanced(GSOAP_INCLUDE_DIR GSOAP_LIBRARIES GSOAP_WSDL2H GSOAP_SOAPCPP2) + +if(GSOAP_FOUND) + if(GSOAP_FIND_REQUIRED AND GSOAP_FIND_VERSION AND ${GSOAP_VERSION} VERSION_LESS ${GSOAP_FIND_VERSION}) + message(SEND_ERROR "Found GSOAP version ${GSOAP_VERSION} less then required ${GSOAP_FIND_VERSION}.") + endif() +endif() + diff --git a/cmake/Modules/FindLibJWT.cmake b/cmake/Modules/FindLibJWT.cmake new file mode 100644 index 000000000..c82065f9d --- /dev/null +++ b/cmake/Modules/FindLibJWT.cmake @@ -0,0 +1,89 @@ +#[=======================================================================[.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}) +mark_as_advanced(LIBJWT_INCLUDE_DIR) + +find_library(LIBJWT_LIBRARY + 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 + FAIL_MESSAGE + "Could NOT find LibJWT with the crypto backend ${LIBJWT_CRYPTO_BACKEND}.") + +if(LIBJWT_FOUND) + set(LIBJWT_LIBRARIES ${LIBJWT_LIBRARY}) + set(LIBJWT_INCLUDE_DIRS ${LIBJWT_INCLUDE_DIR}) + + 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/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/Modules/Pod2Man.cmake b/cmake/Modules/Pod2Man.cmake index 734be239b..f1c0e400a 100644 --- a/cmake/Modules/Pod2Man.cmake +++ b/cmake/Modules/Pod2Man.cmake @@ -53,8 +53,7 @@ MACRO(POD2MAN PODFILE MANFILE SECTION MANPAGE_DEST_PREFIX) SET(MANPAGE_TARGET "man-${MANFILE}") - ADD_CUSTOM_TARGET(${MANPAGE_TARGET} DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${MANFILE}.${SECTION}.gz) - ADD_DEPENDENCIES(man ${MANPAGE_TARGET}) + ADD_CUSTOM_TARGET(${MANPAGE_TARGET} ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${MANFILE}.${SECTION}.gz) INSTALL( FILES ${CMAKE_CURRENT_BINARY_DIR}/${MANFILE}.${SECTION}.gz diff --git a/cmake/compiler/clang/settings.cmake b/cmake/compiler/clang/settings.cmake new file mode 100644 index 000000000..d5ba4204a --- /dev/null +++ b/cmake/compiler/clang/settings.cmake @@ -0,0 +1,43 @@ +target_compile_options(zm-warning-interface + INTERFACE + -Wall + -Wextra + -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 + INTERFACE + -fno-omit-frame-pointer + -fsanitize=address + -fsanitize-recover=address + -fsanitize-address-use-after-scope) + + target_link_options(zm-compile-option-interface + INTERFACE + -fno-omit-frame-pointer + -fsanitize=address + -fsanitize-recover=address + -fsanitize-address-use-after-scope) + + message(STATUS "Clang: Enabled AddressSanitizer (ASan)") +endif() + +if(TSAN) + target_compile_options(zm-compile-option-interface + INTERFACE + -fsanitize=thread) + + target_link_options(zm-compile-option-interface + INTERFACE + -fsanitize=thread) + + message(STATUS "Clang: Enabled ThreadSanitizer (TSan)") +endif() diff --git a/cmake/compiler/gcc/settings.cmake b/cmake/compiler/gcc/settings.cmake new file mode 100644 index 000000000..638bc39b4 --- /dev/null +++ b/cmake/compiler/gcc/settings.cmake @@ -0,0 +1,50 @@ +target_compile_options(zm-warning-interface + INTERFACE + -Wall + $<$,5.0>:-Wconditionally-supported> + -Wextra + -Wformat-security + -Wno-cast-function-type + $<$,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 + -Wno-stringop-truncation) + + target_link_options(zm-compile-option-interface + INTERFACE + -fno-omit-frame-pointer + -fsanitize=address + -fsanitize-recover=address + -fsanitize-address-use-after-scope) + + message(STATUS "GCC: Enabled AddressSanitizer (ASan)") +endif() + +if(TSAN) + target_compile_options(zm-compile-option-interface + INTERFACE + -fsanitize=thread) + + target_link_options(zm-compile-option-interface + INTERFACE + -fsanitize=thread) + + message(STATUS "GCC: Enabled ThreadSanitizer (TSan)") +endif() diff --git a/conf.d/01-system-paths.conf.in b/conf.d/01-system-paths.conf.in index e1aaf0bef..523a8c0c8 100644 --- a/conf.d/01-system-paths.conf.in +++ b/conf.d/01-system-paths.conf.in @@ -46,3 +46,10 @@ ZM_PATH_SWAP=@ZM_TMPDIR@ # Full path to optional arp binary # ZoneMinder will find the arp binary automatically on most systems ZM_PATH_ARP="@ZM_PATH_ARP@" + +# Full path to optional arp-scan binary +# ZoneMinder will find the arp-scan binary automatically on most systems +ZM_PATH_ARP_SCAN="@ZM_PATH_ARP_SCAN@" + +#Full path to shutdown binary +ZM_PATH_SHUTDOWN="@ZM_PATH_SHUTDOWN@" diff --git a/db/CMakeLists.txt b/db/CMakeLists.txt index ed215d35f..00cc7d8a4 100644 --- a/db/CMakeLists.txt +++ b/db/CMakeLists.txt @@ -3,6 +3,8 @@ # 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) +configure_file(zm_update-1.37.4.sql.in "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.37.4.sql" @ONLY) # Glob all database upgrade scripts file(GLOB dbfileslist RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "zm_update-*.sql") @@ -12,6 +14,10 @@ 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_update-1.37.4.sql +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.37.4.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") @@ -19,3 +25,8 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm_create.sql" DESTINATION "${CMAKE_I # install triggers.sql install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/triggers.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db") +# install manufacturers.sql +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/manufacturers.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db") + +# install models.sql +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/models.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db") diff --git a/db/manufacturers.sql b/db/manufacturers.sql new file mode 100644 index 000000000..3761f5cbe --- /dev/null +++ b/db/manufacturers.sql @@ -0,0 +1,24 @@ +INSERT IGNORE INTO Manufacturers VALUES (1, 'Acti'); +INSERT IGNORE INTO Manufacturers VALUES (2, 'Amcrest'); +INSERT IGNORE INTO Manufacturers VALUES (3, 'Airlink101'); +INSERT IGNORE INTO Manufacturers VALUES (4, 'Arecont Vision'); +INSERT IGNORE INTO Manufacturers VALUES (5, 'Axis'); +INSERT IGNORE INTO Manufacturers VALUES (6, 'Dahua'); +INSERT IGNORE INTO Manufacturers VALUES (7, 'D-Link'); +INSERT IGNORE INTO Manufacturers VALUES (8, 'Edimax'); +INSERT IGNORE INTO Manufacturers VALUES (9, 'Foscam'); +INSERT IGNORE INTO Manufacturers VALUES (10, 'Gadspot'); +INSERT IGNORE INTO Manufacturers VALUES (11, 'GrandStream'); +INSERT IGNORE INTO Manufacturers VALUES (12, 'HikVision'); +INSERT IGNORE INTO Manufacturers VALUES (13, 'JVC'); +INSERT IGNORE INTO Manufacturers VALUES (14, 'Maginon'); +INSERT IGNORE INTO Manufacturers VALUES (15, 'Mobotix'); +INSERT IGNORE INTO Manufacturers VALUES (16, 'Oncam Grandeye'); +INSERT IGNORE INTO Manufacturers VALUES (17, 'Panasonic'); +INSERT IGNORE INTO Manufacturers VALUES (18, 'Pelco'); +INSERT IGNORE INTO Manufacturers VALUES (19, 'Sony'); +INSERT IGNORE INTO Manufacturers VALUES (20, 'TP-Link'); +INSERT IGNORE INTO Manufacturers VALUES (21, 'Trendnet'); +INSERT IGNORE INTO Manufacturers VALUES (22, 'VisionTek'); +INSERT IGNORE INTO Manufacturers VALUES (23, 'Vivotek'); +INSERT IGNORE INTO Manufacturers VALUES (24, 'Wansview'); diff --git a/db/models.sql b/db/models.sql new file mode 100644 index 000000000..ffafb76a8 --- /dev/null +++ b/db/models.sql @@ -0,0 +1,56 @@ +/* INSERT INTO Manufacturers VALUES (1, 'Acti'); */ +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A21'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A23'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A24'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A28'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A31'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A310'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A311'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A32'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A41'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A415'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A416'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A418'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A42'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A421'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A43'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A45'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A46'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A48'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A74'); +/* +INSERT INTO Manufacturers VALUES (2, 'Amcrest'); +*/ +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (2, 'IP8M-T2499EW'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (2, 'ASH42-B'); +/* +INSERT INTO Manufacturers VALUES (3, 'Airlink101'); +INSERT INTO Manufacturers VALUES (4, 'Arecont Vision'); +INSERT INTO Manufacturers VALUES (5, 'Axis'); +INSERT INTO Manufacturers VALUES (6, 'Dahua'); +INSERT INTO Manufacturers VALUES (7, 'D-Link'); +*/ +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (7, 'DCS-930L'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (7, 'DCS-932L'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (7, 'DCS-933L'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (7, 'DCS-942L'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (7, 'DCS-5020L'); +/* +INSERT INTO Manufacturers VALUES (8, 'Edimax'); +INSERT INTO Manufacturers VALUES (9, 'Foscam'); +INSERT INTO Manufacturers VALUES (10, 'Gadspot'); +INSERT INTO Manufacturers VALUES (11, 'GrandStream'); +INSERT INTO Manufacturers VALUES (12, 'HikVision'); +INSERT INTO Manufacturers VALUES (13, 'JVC'); +INSERT INTO Manufacturers VALUES (14, 'Maginon'); +INSERT INTO Manufacturers VALUES (15, 'Mobotix'); +INSERT INTO Manufacturers VALUES (16, 'Oncam Grandeye'); +INSERT INTO Manufacturers VALUES (17, 'Panasonic'); +INSERT INTO Manufacturers VALUES (18, 'Pelco'); +INSERT INTO Manufacturers VALUES (19, 'Sony'); +INSERT INTO Manufacturers VALUES (20, 'TP-Link'); +INSERT INTO Manufacturers VALUES (21, 'Trendnet'); +INSERT INTO Manufacturers VALUES (22, 'VisionTek'); +INSERT INTO Manufacturers VALUES (23, 'Vivotek'); +INSERT INTO Manufacturers VALUES (24, 'Wansview'); +*/ diff --git a/db/triggers.sql b/db/triggers.sql index 87c8465b4..58f09bcbf 100644 --- a/db/triggers.sql +++ b/db/triggers.sql @@ -3,10 +3,10 @@ delimiter // DROP TRIGGER IF EXISTS Events_Hour_delete_trigger// CREATE TRIGGER Events_Hour_delete_trigger BEFORE DELETE ON Events_Hour FOR EACH ROW BEGIN - UPDATE Monitors SET + UPDATE Event_Summaries SET HourEvents = GREATEST(COALESCE(HourEvents,1)-1,0), HourEventDiskSpace=GREATEST(COALESCE(HourEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; END; // @@ -20,10 +20,10 @@ FOR EACH ROW set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); IF ( diff ) THEN IF ( NEW.MonitorID != OLD.MonitorID ) THEN - UPDATE Monitors SET HourEventDiskSpace=GREATEST(COALESCE(HourEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Monitors.Id=OLD.MonitorId; - UPDATE Monitors SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Monitors.Id=NEW.MonitorId; + UPDATE Event_Summaries SET HourEventDiskSpace=GREATEST(COALESCE(HourEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Event_Summaries.MonitorId=OLD.MonitorId; + UPDATE Event_Summaries SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Event_Summaries.MonitorId=NEW.MonitorId; ELSE - UPDATE Monitors SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)+diff WHERE Monitors.Id=NEW.MonitorId; + UPDATE Event_Summaries SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)+diff WHERE Event_Summaries.MonitorId=NEW.MonitorId; END IF; END IF; END; @@ -32,10 +32,10 @@ FOR EACH ROW DROP TRIGGER IF EXISTS Events_Day_delete_trigger// CREATE TRIGGER Events_Day_delete_trigger BEFORE DELETE ON Events_Day FOR EACH ROW BEGIN - UPDATE Monitors SET + UPDATE Event_Summaries SET DayEvents = GREATEST(COALESCE(DayEvents,1)-1,0), DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; END; // @@ -48,10 +48,10 @@ FOR EACH ROW set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); IF ( diff ) THEN IF ( NEW.MonitorID != OLD.MonitorID ) THEN - UPDATE Monitors SET DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Monitors.Id=OLD.MonitorId; - UPDATE Monitors SET DayEventDiskSpace=COALESCE(DayEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Monitors.Id=NEW.MonitorId; + UPDATE Event_Summaries SET DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Event_Summaries.MonitorId=OLD.MonitorId; + UPDATE Event_Summaries SET DayEventDiskSpace=COALESCE(DayEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Event_Summaries.MonitorId=NEW.MonitorId; ELSE - UPDATE Monitors SET DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)+diff,0) WHERE Monitors.Id=NEW.MonitorId; + UPDATE Event_Summaries SET DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)+diff,0) WHERE Event_Summaries.MonitorId=NEW.MonitorId; END IF; END IF; END; @@ -61,10 +61,10 @@ FOR EACH ROW DROP TRIGGER IF EXISTS Events_Week_delete_trigger// CREATE TRIGGER Events_Week_delete_trigger BEFORE DELETE ON Events_Week FOR EACH ROW BEGIN - UPDATE Monitors SET + UPDATE Event_Summaries SET WeekEvents = GREATEST(COALESCE(WeekEvents,1)-1,0), WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; END; // @@ -77,10 +77,10 @@ FOR EACH ROW set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); IF ( diff ) THEN IF ( NEW.MonitorID != OLD.MonitorID ) THEN - UPDATE Monitors SET WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Monitors.Id=OLD.MonitorId; - UPDATE Monitors SET WeekEventDiskSpace=COALESCE(WeekEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Monitors.Id=NEW.MonitorId; + UPDATE Event_Summaries SET WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Event_Summaries.MonitorId=OLD.MonitorId; + UPDATE Event_Summaries SET WeekEventDiskSpace=COALESCE(WeekEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Event_Summaries.MonitorId=NEW.MonitorId; ELSE - UPDATE Monitors SET WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)+diff,0) WHERE Monitors.Id=NEW.MonitorId; + UPDATE Event_Summaries SET WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)+diff,0) WHERE Event_Summaries.MonitorId=NEW.MonitorId; END IF; END IF; END; @@ -89,10 +89,10 @@ FOR EACH ROW DROP TRIGGER IF EXISTS Events_Month_delete_trigger// CREATE TRIGGER Events_Month_delete_trigger BEFORE DELETE ON Events_Month FOR EACH ROW BEGIN - UPDATE Monitors SET + UPDATE Event_Summaries SET MonthEvents = GREATEST(COALESCE(MonthEvents,1)-1,0), MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; END; // @@ -105,10 +105,10 @@ FOR EACH ROW set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); IF ( diff ) THEN IF ( NEW.MonitorID != OLD.MonitorID ) THEN - UPDATE Monitors SET MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)-COALESCE(OLD.DiskSpace),0) WHERE Monitors.Id=OLD.MonitorId; - UPDATE Monitors SET MonthEventDiskSpace=COALESCE(MonthEventDiskSpace,0)+COALESCE(NEW.DiskSpace) WHERE Monitors.Id=NEW.MonitorId; + UPDATE Event_Summaries SET MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)-COALESCE(OLD.DiskSpace),0) WHERE Event_Summaries.MonitorId=OLD.MonitorId; + UPDATE Event_Summaries SET MonthEventDiskSpace=COALESCE(MonthEventDiskSpace,0)+COALESCE(NEW.DiskSpace) WHERE Event_Summaries.MonitorId=NEW.MonitorId; ELSE - UPDATE Monitors SET MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)+diff,0) WHERE Monitors.Id=NEW.MonitorId; + UPDATE Event_Summaries SET MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)+diff,0) WHERE Event_Summaries.MonitorId=NEW.MonitorId; END IF; END IF; END; @@ -126,14 +126,14 @@ BEGIN set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); IF ( NEW.StorageId = OLD.StorageID ) THEN IF ( diff ) THEN - UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) + diff,0) WHERE Id = OLD.StorageId; + UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) + diff,0) WHERE Storage.Id = OLD.StorageId; END IF; ELSE IF ( NEW.DiskSpace ) THEN - UPDATE Storage SET DiskSpace = COALESCE(DiskSpace,0) + NEW.DiskSpace WHERE Id = NEW.StorageId; + UPDATE Storage SET DiskSpace = COALESCE(DiskSpace,0) + NEW.DiskSpace WHERE Storage.Id = NEW.StorageId; END IF; IF ( OLD.DiskSpace ) THEN - UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) - OLD.DiskSpace,0) WHERE Id = OLD.StorageId; + UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) - OLD.DiskSpace,0) WHERE Storage.Id = OLD.StorageId; END IF; END IF; @@ -145,20 +145,21 @@ BEGIN IF ( NEW.Archived != OLD.Archived ) THEN IF ( NEW.Archived ) THEN INSERT INTO Events_Archived (EventId,MonitorId,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.DiskSpace); - UPDATE Monitors SET ArchivedEvents = COALESCE(ArchivedEvents,0)+1, ArchivedEventDiskSpace = COALESCE(ArchivedEventDiskSpace,0) + COALESCE(NEW.DiskSpace,0) WHERE Id=NEW.MonitorId; + INSERT INTO Event_Summaries (MonitorId,ArchivedEvents,ArchivedEventDiskSpace) VALUES (NEW.MonitorId,1,NEW.DiskSpace) ON DUPLICATE KEY + UPDATE ArchivedEvents = COALESCE(ArchivedEvents,0)+1, ArchivedEventDiskSpace = COALESCE(ArchivedEventDiskSpace,0) + COALESCE(NEW.DiskSpace,0); ELSEIF ( OLD.Archived ) THEN DELETE FROM Events_Archived WHERE EventId=OLD.Id; - UPDATE Monitors + UPDATE Event_Summaries SET ArchivedEvents = GREATEST(COALESCE(ArchivedEvents,0)-1,0), ArchivedEventDiskSpace = GREATEST(COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; ELSE IF ( OLD.DiskSpace != NEW.DiskSpace ) THEN UPDATE Events_Archived SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; - UPDATE Monitors SET + UPDATE Event_Summaries SET ArchivedEventDiskSpace = GREATEST(COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0) + COALESCE(NEW.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; END IF; END IF; ELSEIF ( NEW.Archived AND diff ) THEN @@ -166,10 +167,10 @@ BEGIN END IF; IF ( diff ) THEN - UPDATE Monitors + UPDATE Event_Summaries SET TotalEventDiskSpace = GREATEST(COALESCE(TotalEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0) + COALESCE(NEW.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; END IF; END; @@ -185,17 +186,17 @@ CREATE TRIGGER event_insert_trigger AFTER INSERT ON Events FOR EACH ROW BEGIN - INSERT INTO Events_Hour (EventId,MonitorId,StartTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartTime,0); - INSERT INTO Events_Day (EventId,MonitorId,StartTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartTime,0); - INSERT INTO Events_Week (EventId,MonitorId,StartTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartTime,0); - INSERT INTO Events_Month (EventId,MonitorId,StartTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartTime,0); - UPDATE Monitors SET + INSERT INTO Events_Hour (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Day (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Week (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Month (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Event_Summaries (MonitorId,HourEvents,DayEvents,WeekEvents,MonthEvents,TotalEvents) VALUES (NEW.MonitorId,1,1,1,1,1) ON DUPLICATE KEY + UPDATE HourEvents = COALESCE(HourEvents,0)+1, DayEvents = COALESCE(DayEvents,0)+1, WeekEvents = COALESCE(WeekEvents,0)+1, MonthEvents = COALESCE(MonthEvents,0)+1, - TotalEvents = COALESCE(TotalEvents,0)+1 - WHERE Id=NEW.MonitorId; + TotalEvents = COALESCE(TotalEvents,0)+1; END; // @@ -205,7 +206,7 @@ CREATE TRIGGER event_delete_trigger BEFORE DELETE ON Events FOR EACH ROW BEGIN IF ( OLD.DiskSpace ) THEN - UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) - COALESCE(OLD.DiskSpace,0),0) WHERE Id = OLD.StorageId; + UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) - COALESCE(OLD.DiskSpace,0),0) WHERE Storage.Id = OLD.StorageId; END IF; DELETE FROM Events_Hour WHERE EventId=OLD.Id; DELETE FROM Events_Day WHERE EventId=OLD.Id; @@ -213,17 +214,17 @@ BEGIN DELETE FROM Events_Month WHERE EventId=OLD.Id; IF ( OLD.Archived ) THEN DELETE FROM Events_Archived WHERE EventId=OLD.Id; - UPDATE Monitors SET + UPDATE Event_Summaries SET ArchivedEvents = GREATEST(COALESCE(ArchivedEvents,1) - 1,0), ArchivedEventDiskSpace = GREATEST(COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0),0), TotalEvents = GREATEST(COALESCE(TotalEvents,1) - 1,0), TotalEventDiskSpace = GREATEST(COALESCE(TotalEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; ELSE - UPDATE Monitors SET + UPDATE Event_Summaries SET TotalEvents = GREATEST(COALESCE(TotalEvents,1)-1,0), TotalEventDiskSpace=GREATEST(COALESCE(TotalEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; END IF; END; @@ -233,14 +234,14 @@ DROP TRIGGER IF EXISTS Zone_Insert_Trigger// CREATE TRIGGER Zone_Insert_Trigger AFTER INSERT ON Zones FOR EACH ROW BEGIN - UPDATE Monitors SET ZoneCount=(SELECT COUNT(*) FROM Zones WHERE MonitorId=NEW.MonitorId) WHERE Id=NEW.MonitorID; + UPDATE Monitors SET ZoneCount=(SELECT COUNT(*) FROM Zones WHERE MonitorId=NEW.MonitorId) WHERE Monitors.Id=NEW.MonitorID; END // DROP TRIGGER IF EXISTS Zone_Delete_Trigger// CREATE TRIGGER Zone_Delete_Trigger AFTER DELETE ON Zones FOR EACH ROW BEGIN - UPDATE Monitors SET ZoneCount=(SELECT COUNT(*) FROM Zones WHERE MonitorId=OLD.MonitorId) WHERE Id=OLD.MonitorID; + UPDATE Monitors SET ZoneCount=(SELECT COUNT(*) FROM Zones WHERE MonitorId=OLD.MonitorId) WHERE Monitors.Id=OLD.MonitorID; END // diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index c9b2b6ea1..040bc593c 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -39,6 +39,7 @@ CREATE TABLE `Config` ( `Help` text, `Category` varchar(32) NOT NULL default '', `Readonly` tinyint(3) unsigned NOT NULL default '0', + `Private` BOOLEAN NOT NULL DEFAULT FALSE, `Requires` text, PRIMARY KEY (`Name`) ) ENGINE=@ZM_MYSQL_ENGINE@; @@ -63,7 +64,7 @@ DROP TABLE IF EXISTS `Controls`; CREATE TABLE `Controls` ( `Id` int(10) unsigned NOT NULL auto_increment, `Name` varchar(64) NOT NULL default '', - `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL','WebSite','NVSocket') NOT NULL default 'Local', + `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL','WebSite','NVSocket','VNC') NOT NULL default 'Local', `Protocol` varchar(64) default NULL, `CanWake` tinyint(3) unsigned NOT NULL default '0', `CanSleep` tinyint(3) unsigned NOT NULL default '0', @@ -188,9 +189,9 @@ CREATE TABLE `Events` ( `StorageId` smallint(5) unsigned default 0, `SecondaryStorageId` smallint(5) unsigned default 0, `Name` varchar(64) NOT NULL default '', - `Cause` varchar(32) NOT NULL default '', - `StartTime` datetime default NULL, - `EndTime` datetime default NULL, + `Cause` TEXT, + `StartDateTime` datetime default NULL, + `EndDateTime` datetime default NULL, `Width` smallint(5) unsigned NOT NULL default '0', `Height` smallint(5) unsigned NOT NULL default '0', `Length` decimal(10,2) NOT NULL default '0.00', @@ -216,52 +217,52 @@ CREATE TABLE `Events` ( PRIMARY KEY (`Id`), KEY `Events_MonitorId_idx` (`MonitorId`), KEY `Events_StorageId_idx` (`StorageId`), - KEY `Events_StartTime_idx` (`StartTime`), - KEY `Events_EndTime_DiskSpace` (`EndTime`,`DiskSpace`) + KEY `Events_StartDateTime_idx` (`StartDateTime`), + KEY `Events_EndDateTime_DiskSpace` (`EndDateTime`,`DiskSpace`) ) ENGINE=@ZM_MYSQL_ENGINE@; DROP TABLE IF EXISTS `Events_Hour`; CREATE TABLE `Events_Hour` ( `EventId` BIGINT unsigned NOT NULL, `MonitorId` int(10) unsigned NOT NULL, - `StartTime` datetime default NULL, + `StartDateTime` datetime default NULL, `DiskSpace` bigint default NULL, PRIMARY KEY (`EventId`), KEY `Events_Hour_MonitorId_idx` (`MonitorId`), - KEY `Events_Hour_StartTime_idx` (`StartTime`) + KEY `Events_Hour_StartDateTime_idx` (`StartDateTime`) ) ENGINE=@ZM_MYSQL_ENGINE@; DROP TABLE IF EXISTS `Events_Day`; CREATE TABLE `Events_Day` ( `EventId` BIGINT unsigned NOT NULL, `MonitorId` int(10) unsigned NOT NULL, - `StartTime` datetime default NULL, + `StartDateTime` datetime default NULL, `DiskSpace` bigint default NULL, PRIMARY KEY (`EventId`), KEY `Events_Day_MonitorId_idx` (`MonitorId`), - KEY `Events_Day_StartTime_idx` (`StartTime`) + KEY `Events_Day_StartDateTime_idx` (`StartDateTime`) ) ENGINE=@ZM_MYSQL_ENGINE@; DROP TABLE IF EXISTS `Events_Week`; CREATE TABLE `Events_Week` ( `EventId` BIGINT unsigned NOT NULL, `MonitorId` int(10) unsigned NOT NULL, - `StartTime` datetime default NULL, + `StartDateTime` datetime default NULL, `DiskSpace` bigint default NULL, PRIMARY KEY (`EventId`), KEY `Events_Week_MonitorId_idx` (`MonitorId`), - KEY `Events_Week_StartTime_idx` (`StartTime`) + KEY `Events_Week_StartDateTime_idx` (`StartDateTime`) ) ENGINE=@ZM_MYSQL_ENGINE@; DROP TABLE IF EXISTS `Events_Month`; CREATE TABLE `Events_Month` ( `EventId` BIGINT unsigned NOT NULL, `MonitorId` int(10) unsigned NOT NULL, - `StartTime` datetime default NULL, + `StartDateTime` datetime default NULL, `DiskSpace` bigint default NULL, PRIMARY KEY (`EventId`), KEY `Events_Month_MonitorId_idx` (`MonitorId`), - KEY `Events_Month_StartTime_idx` (`StartTime`) + KEY `Events_Month_StartDateTime_idx` (`StartDateTime`) ) ENGINE=@ZM_MYSQL_ENGINE@; @@ -282,11 +283,17 @@ DROP TABLE IF EXISTS `Filters`; CREATE TABLE `Filters` ( `Id` int(10) unsigned NOT NULL auto_increment, `Name` varchar(64) NOT NULL default '', + `UserId` int(10) unsigned, + `ExecuteInterval` int(10) unsigned NOT NULL default '60', `Query_json` text NOT NULL, `AutoArchive` tinyint(3) unsigned NOT NULL default '0', + `AutoUnarchive` tinyint(3) unsigned NOT NULL default '0', `AutoVideo` tinyint(3) unsigned NOT NULL default '0', `AutoUpload` tinyint(3) unsigned NOT NULL default '0', `AutoEmail` tinyint(3) unsigned NOT NULL default '0', + `EmailTo` TEXT, + `EmailSubject` TEXT, + `EmailBody` TEXT, `AutoMessage` tinyint(3) unsigned NOT NULL default '0', `AutoExecute` tinyint(3) unsigned NOT NULL default '0', `AutoExecuteCmd` tinytext, @@ -298,6 +305,7 @@ CREATE TABLE `Filters` ( `UpdateDiskSpace` tinyint(3) unsigned NOT NULL default '0', `Background` tinyint(1) unsigned NOT NULL default '0', `Concurrent` tinyint(1) unsigned NOT NULL default '0', + `LockRows` tinyint(1) unsigned NOT NULL default '0', PRIMARY KEY (`Id`), KEY `Name` (`Name`) ) ENGINE=@ZM_MYSQL_ENGINE@; @@ -310,6 +318,7 @@ DROP TABLE IF EXISTS `Frames`; CREATE TABLE `Frames` ( `Id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `EventId` BIGINT UNSIGNED NOT NULL default '0', + FOREIGN KEY (`EventId`) REFERENCES `Events` (`Id`) ON DELETE CASCADE, `FrameId` int(10) unsigned NOT NULL default '0', `Type` enum('Normal','Bulk','Alarm') NOT NULL default 'Normal', `TimeStamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, @@ -330,6 +339,7 @@ CREATE TABLE `Groups` ( `Id` int(10) unsigned NOT NULL auto_increment, `Name` varchar(64) NOT NULL default '', `ParentId` int(10) unsigned, + FOREIGN KEY (`ParentId`) REFERENCES `Groups` (`Id`) ON DELETE CASCADE, PRIMARY KEY (`Id`) ) ENGINE=@ZM_MYSQL_ENGINE@; @@ -341,7 +351,9 @@ DROP TABLE IF EXISTS `Groups_Monitors`; CREATE TABLE `Groups_Monitors` ( `Id` INT(10) unsigned NOT NULL auto_increment, `GroupId` int(10) unsigned NOT NULL, + FOREIGN KEY (`GroupId`) REFERENCES `Groups` (`Id`) ON DELETE CASCADE, `MonitorId` int(10) unsigned NOT NULL, + FOREIGN KEY (`MonitorId`) REFERENCES `Monitors` (`Id`) ON DELETE CASCADE, PRIMARY KEY (`Id`) ) ENGINE=@ZM_MYSQL_ENGINE@; @@ -402,8 +414,9 @@ CREATE TABLE `Models` ( DROP TABLE IF EXISTS `MonitorPresets`; CREATE TABLE `MonitorPresets` ( `Id` int(10) unsigned NOT NULL auto_increment, + `ModelId` int unsigned, FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id), `Name` varchar(64) NOT NULL default '', - `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL','WebSite','NVSocket') NOT NULL default 'Local', + `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL','WebSite','NVSocket','VNC') NOT NULL default 'Local', `Device` tinytext, `Channel` tinyint(3) unsigned default NULL, `Format` int(10) unsigned default NULL, @@ -437,11 +450,24 @@ CREATE TABLE `Monitors` ( `Notes` TEXT, `ServerId` int(10) unsigned, `StorageId` smallint(5) unsigned default 0, - `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL','WebSite','NVSocket') NOT NULL default 'Local', + `ManufacturerId` int unsigned, FOREIGN KEY (`ManufacturerId`) REFERENCES `Manufacturers` (Id), + `ModelId` int unsigned, FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id), + `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL','WebSite','NVSocket','VNC') NOT NULL default 'Local', `Function` enum('None','Monitor','Modect','Record','Mocord','Nodect') NOT NULL default 'Monitor', `Enabled` tinyint(3) unsigned NOT NULL default '1', + `DecodingEnabled` tinyint(3) unsigned NOT NULL default '1', + `JanusEnabled` BOOLEAN NOT NULL default false, + `JanusAudioEnabled` BOOLEAN NOT NULL default false, `LinkedMonitors` varchar(255), `Triggers` set('X10') NOT NULL default '', + `EventStartCommand` VARCHAR(255) NOT NULL DEFAULT '', + `EventEndCommand` VARCHAR(255) NOT NULL DEFAULT '', + `ONVIF_URL` VARCHAR(255) NOT NULL DEFAULT '', + `ONVIF_Username` VARCHAR(64) NOT NULL DEFAULT '', + `ONVIF_Password` VARCHAR(64) NOT NULL DEFAULT '', + `ONVIF_Options` VARCHAR(64) NOT NULL DEFAULT '', + `ONVIF_Event_Listener` BOOLEAN NOT NULL DEFAULT FALSE, + `use_Amcrest_API` BOOLEAN NOT NULL DEFAULT FALSE, `Device` tinytext NOT NULL default '', `Channel` tinyint(3) unsigned NOT NULL default '0', `Format` int(10) unsigned NOT NULL default '0', @@ -453,6 +479,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), @@ -466,7 +493,8 @@ CREATE TABLE `Monitors` ( `DecoderHWAccelDevice` varchar(255), `SaveJPEGs` TINYINT NOT NULL DEFAULT '3' , `VideoWriter` TINYINT NOT NULL DEFAULT '0', - `OutputCodec` enum('h264','mjpeg','mpeg1','mpeg2'), + `OutputCodec` int(10) unsigned NOT NULL default 0, + `Encoder` varchar(32), `OutputContainer` enum('auto','mp4','mkv'), `EncoderParameters` TEXT, `RecordAudio` TINYINT NOT NULL DEFAULT '0', @@ -476,15 +504,16 @@ CREATE TABLE `Monitors` ( `Hue` mediumint(7) NOT NULL default '-1', `Colour` mediumint(7) NOT NULL default '-1', `EventPrefix` varchar(32) NOT NULL default 'Event-', - `LabelFormat` varchar(64) default '%N - %y/%m/%d %H:%M:%S', + `LabelFormat` varchar(64), `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', @@ -506,6 +535,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', @@ -514,20 +544,13 @@ CREATE TABLE `Monitors` ( `WebColour` varchar(32) NOT NULL default 'red', `Exif` tinyint(1) unsigned NOT NULL default '0', `Sequence` smallint(5) unsigned default NULL, - `TotalEvents` int(10) default NULL, - `TotalEventDiskSpace` bigint default NULL, - `HourEvents` int(10) default NULL, - `HourEventDiskSpace` bigint default NULL, - `DayEvents` int(10) default NULL, - `DayEventDiskSpace` bigint default NULL, - `WeekEvents` int(10) default NULL, - `WeekEventDiskSpace` bigint default NULL, - `MonthEvents` int(10) default NULL, - `MonthEventDiskSpace` bigint default NULL, - `ArchivedEvents` int(10) default NULL, - `ArchivedEventDiskSpace` bigint default NULL, `ZoneCount` TINYINT NOT NULL DEFAULT 0, `Refresh` int(10) unsigned default NULL, + `Latitude` DECIMAL(10,8), + `Longitude` DECIMAL(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@; @@ -541,7 +564,26 @@ CREATE TABLE `Monitor_Status` ( `AnalysisFPS` DECIMAL(5,2) NOT NULL default 0, `CaptureBandwidth` INT NOT NULL default 0, PRIMARY KEY (`MonitorId`) -) ENGINE=MEMORY; +) ENGINE=@ZM_MYSQL_ENGINE@; + +DROP TABLE IF EXISTS `Event_Summaries`; +CREATE TABLE `Event_Summaries` ( + `MonitorId` int(10) unsigned NOT NULL, + `TotalEvents` int(10) default NULL, + `TotalEventDiskSpace` bigint default NULL, + `HourEvents` int(10) default NULL, + `HourEventDiskSpace` bigint default NULL, + `DayEvents` int(10) default NULL, + `DayEventDiskSpace` bigint default NULL, + `WeekEvents` int(10) default NULL, + `WeekEventDiskSpace` bigint default NULL, + `MonthEvents` int(10) default NULL, + `MonthEventDiskSpace` bigint default NULL, + `ArchivedEvents` int(10) default NULL, + `ArchivedEventDiskSpace` bigint default NULL, + PRIMARY KEY (`MonitorId`) +) ENGINE=@ZM_MYSQL_ENGINE@; + -- -- Table structure for table `States` -- PP - Added IsActive to track custom run states @@ -597,8 +639,11 @@ DROP TABLE IF EXISTS `Stats`; CREATE TABLE `Stats` ( `Id` int(10) unsigned NOT NULL AUTO_INCREMENT, `MonitorId` int(10) unsigned NOT NULL default '0', + FOREIGN KEY (`MonitorId`) REFERENCES `Monitors` (`Id`) ON DELETE CASCADE, `ZoneId` int(10) unsigned NOT NULL default '0', + FOREIGN KEY (`ZoneId`) REFERENCES `Zones` (`Id`) ON DELETE CASCADE, `EventId` BIGINT UNSIGNED NOT NULL, + FOREIGN KEY (`EventId`) REFERENCES `Events` (`Id`) ON DELETE CASCADE, `FrameId` int(10) unsigned NOT NULL default '0', `PixelDiff` tinyint(3) unsigned NOT NULL default '0', `AlarmPixels` int(10) unsigned NOT NULL default '0', @@ -648,11 +693,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@; @@ -693,6 +740,7 @@ DROP TABLE IF EXISTS `Zones`; CREATE TABLE `Zones` ( `Id` int(10) unsigned NOT NULL auto_increment, `MonitorId` int(10) unsigned NOT NULL default '0', + FOREIGN KEY (`MonitorId`) REFERENCES `Monitors` (`Id`) ON DELETE CASCADE, `Name` varchar(64) NOT NULL default '', `Type` enum('Active','Inclusive','Exclusive','Preclusive','Inactive','Privacy') NOT NULL default 'Active', `Units` enum('Pixels','Percent') NOT NULL default 'Pixels', @@ -730,13 +778,14 @@ CREATE TABLE `Storage` ( `Scheme` enum('Deep','Medium','Shallow') NOT NULL default 'Medium', `ServerId` int(10) unsigned, `DoDelete` BOOLEAN NOT NULL DEFAULT true, + `Enabled` BOOLEAN NOT NULL DEFAULT true, PRIMARY KEY (`Id`) ) ENGINE=@ZM_MYSQL_ENGINE@; -- -- Create a default storage location -- -insert into Storage VALUES (NULL, '@ZM_DIR_EVENTS@', 'Default', 'local', NULL, NULL, 'Medium', 0, true ); +insert into Storage VALUES (NULL, '@ZM_DIR_EVENTS@', 'Default', 'local', NULL, NULL, 'Medium', 0, true, true ); /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; @@ -753,46 +802,158 @@ 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 -- -insert into Filters values (NULL,'PurgeWhenFull','{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="}],"limit":100,"sort_asc":1}', +INSERT INTO `Filters` + ( + `Name`, + `Query_json`, + `AutoArchive`, + `AutoVideo`, + `AutoUpload`, + `AutoEmail`, + `EmailTo`, + `EmailSubject`, + `EmailBody`, + `AutoMessage`, + `AutoExecute`, + `AutoExecuteCmd`, + `AutoDelete`, + `AutoMove`, + `AutoMoveTo`, + `AutoCopy`, + `AutoCopyTo`, + `UpdateDiskSpace`, + `UserId`, + `Background`, + `Concurrent` + ) + VALUES + ( + 'PurgeWhenFull', + '{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="},{"cnj":"and","obr":"0","attr":"EndDateTime","op":"IS NOT","val":"NULL","cbr":"0"}],"limit":100,"sort_asc":1}', + 0/*AutoArchive*/, + 0/*AutoVideo*/, + 0/*AutoUpload*/, + 0/*AutoEmail*/, + ''/*EmailTo*/, + ''/*EmailSubject*/, + ''/*EmailBody*/, + 0/*AutoMessage*/, + 0/*AutoExecute*/,'', + 1/*AutoDelete*/, + 0/*AutoMove*/,0/*MoveTo*/, + 0/*AutoCopy*/,0/*CopyTo*/, + 0/*UpdateDiskSpace*/, + 1/*UserId = admin*/, + 1/*Background*/, + 0/*Concurrent*/ + ); +INSERT INTO `Filters` + ( + `Name`, + `Query_json`, + `AutoArchive`, + `AutoVideo`, + `AutoUpload`, + `AutoEmail`, + `EmailTo`, + `EmailSubject`, + `EmailBody`, + `AutoMessage`, + `AutoExecute`, + `AutoExecuteCmd`, + `AutoDelete`, + `AutoMove`, + `AutoMoveTo`, + `AutoCopy`, + `AutoCopyTo`, + `UpdateDiskSpace`, + `UserId`, + `Background`, + `Concurrent` + ) +VALUES ( + 'Update DiskSpace', + '{"terms":[{"attr":"DiskSpace","op":"IS","val":"NULL"},{"cnj":"and","obr":"0","attr":"EndDateTime","op":"IS NOT","val":"NULL","cbr":"0"}]}', 0/*AutoArchive*/, 0/*AutoVideo*/, 0/*AutoUpload*/, 0/*AutoEmail*/, + ''/*EmailTo*/, + ''/*EmailSubject*/, + ''/*EmailBody*/, 0/*AutoMessage*/, 0/*AutoExecute*/,'', - 1/*AutoDelete*/, + 0/*AutoDelete*/, 0/*AutoMove*/,0/*MoveTo*/, 0/*AutoCopy*/,0/*CopyTo*/, - 0/*UpdateDiskSpace*/,1/*Background*/,0/*Concurrent*/); -insert into Filters values (NULL,'Update DiskSpace','{"terms":[{"attr":"DiskSpace","op":"IS","val":"NULL"}]}',0,0,0,0,0,0,'',0,0,0,0,0,1,1,0); + 1/*UpdateDiskSpace*/, + 1/*UserId=admin*/, + 1/*Background*/, + 0/*Concurrent*/ +); -- -- 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); @@ -808,6 +969,7 @@ INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-423','Ffmpeg','Reolink',0,0,1,0 INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-411','Ffmpeg','Reolink',0,0,1,0,1,0,0,0,1,0,0,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-420','Ffmpeg','Reolink',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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'D-LINK DCS-3415','Remote','DCS3415',0,0,0,0,1,0,0,0,0,0,0,0,0,0,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,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,'D-Link DCS-5020L','Remote','DCS5020L',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,0,0,0,0,0,0,0,0,0,0,0,0,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,24,1,0,1,1,1,0,1,0,1,0,0,1,30,0,0,0,0,0,1,0,0,1,30,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'IOS Camera','Ffmpeg','IPCAMIOS',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,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,0,0,0,0,0,0,1,0,1,0,1,0,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,'Dericam P2','Ffmpeg','DericamP2',0,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,0,0,0,0,0,0,0,0,0,0,0,0,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,1,0,0,0,1,1,0,0,0,0,1,1,45,0,0,1,0,0,0,0,1,1,45,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Trendnet','Remote','Trendnet',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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,1,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); @@ -819,78 +981,82 @@ INSERT INTO `Controls` VALUES (NULL,'Amcrest HTTP API','Ffmpeg','Amcrest_HTTP',0 -- -- Add some monitor preset values -- -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&req_fps=5',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 640x480, mpjpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 640x480, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&req_fps=5',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 640x480, jpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, mpjpeg, B&W','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&color=0',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 640x480, mpjpeg, B&W','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&color=0',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240',NULL,320,240,3,NULL,1,4,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 320x240, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&req_fps=5',NULL,320,240,3,NULL,1,4,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,NULL,1,4,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,5.0,1,4,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 640x480, mpjpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480',NULL,640,480,3,NULL,1,4,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 640x480, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&req_fps=5',NULL,640,480,3,NULL,1,4,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 640x480, jpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,NULL,1,4,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,5.0,1,4,NULL,':',100,100); -INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, unicast','Remote','rtsp',0,255,'rtsp','rtpUni','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, multicast','Remote','rtsp',0,255,'rtsp','rtpMulti','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP','Remote','rtsp',0,255,'rtsp','rtpRtsp','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP/HTTP','Remote',NULL,NULL,NULL,'rtsp','rtpRtspHttp','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'D-link DCS-930L, 640x480, mjpeg','Remote','http',0,0,'http','simple','',80,'/mjpeg.cgi',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 640x480, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 640x480, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,1,5,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,1,5,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,1,5,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 640x480, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,1,5,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 640x480, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,1,5,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,5.0,1,5,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, jpeg','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','',80,'/GetData.cgi',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'IP Webcam by Pavel Khlebovich 1920x1080','Remote','/dev/video','0',255,'http','simple','','8080','/video','',1920,1080,0,NULL,0,'0','','',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'VEO Observer, jpeg','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Blue Net Video Server, jpeg','Remote','http',0,0,'http','simple','',80,'/cgi-bin/image.cgi?control=0&id=admin&passwd=admin',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT into MonitorPresets VALUES (NULL,'ACTi IP, mpeg4, unicast','Remote',NULL,NULL,NULL,'rtsp','rtpUni','',7070,'','/track',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp:///axis-media/media.amp?videocodec=h264',NULL,NULL,NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Vivotek FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp://:554/live.sdp',NULL,NULL,NULL,352,240,NULL,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp:///axis-media/media.amp',NULL,NULL,NULL,640,480,NULL,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'ACTi TCM FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp://admin:123456@:7070',NULL,NULL,NULL,320,240,NULL,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), PAL, 320x240','Local','/dev/video',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), PAL, 320x240, max 5 FPS','Local','/dev/video',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), PAL, 640x480','Local','/dev/video',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), PAL, 640x480, max 5 FPS','Local','/dev/video',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), NTSC, 320x240','Local','/dev/video',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), NTSC, 320x240, max 5 FPS','Local','/dev/video',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), NTSC, 640x480','Local','/dev/video',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), NTSC, 640x480, max 5 FPS','Local','/dev/video',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), PAL, 320x240','Local','/dev/video',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), PAL, 320x240, max 5 FPS','Local','/dev/video',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), PAL, 640x480','Local','/dev/video',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), PAL, 640x480, max 5 FPS','Local','/dev/video',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), NTSC, 320x240','Local','/dev/video',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), NTSC, 320x240, max 5 FPS','Local','/dev/video',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), NTSC, 640x480','Local','/dev/video',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), NTSC, 640x480, max 5 FPS','Local','/dev/video',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Remote ZoneMinder','Remote',NULL,NULL,NULL,'http','simple','',80,'/cgi-bin/nph-zms?mode=jpeg&monitor=&scale=100&maxfps=5&buffer=0',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Foscam FI8620 FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://:@:554/11',NULL,704,576,0,NULL,1,'10','','',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Foscam FI8608W FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://:@:554/11',NULL,640,480,0,NULL,1,'11','','',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Foscam FI9821W FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://:@:88/videoMain',NULL,1280,720,0,NULL,1,'12','','',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Loftek Sentinel PTZ, 640x480, mjpeg','Remote','http',0,0,NULL,NULL,'','80','/videostream.cgi?user=&pwd=&resolution=32&rate=11',NULL,640,480,4,NULL,1,'13','',':@',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Airlink 777W PTZ, 640x480, mjpeg','Remote','http',0,0,NULL,NULL,':@','80','/cgi/mjpg/mjpg.cgi',NULL,640,480,4,NULL,1,'7','',':@',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'SunEyes SP-P1802SWPTZ','Libvlc','/dev/video','0',255,'','rtpMulti','','80','rtsp://:554/11','',1920,1080,0,0.00,1,'16','-speed=64',':',100,33); -INSERT INTO MonitorPresets VALUES (NULL,'Qihan IP, 1280x720, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp',NULL,554,'rtsp:///tcp_live/ch0_0',NULL,1280,720,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Qihan IP, 1920x1080, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp',NULL,554,'rtsp:///tcp_live/ch0_0',NULL,1920,1080,3,NULL,0,NULL,NULL,NULL,100,100); + +INSERT into MonitorPresets VALUES (NULL,NULL,'Amcrest, IP8M-T2499EW 640x480, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp','NULL',554,'rtsp://:@/cam/realmonitor?channel=1&subtype=1',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT into MonitorPresets VALUES (NULL,NULL,'Amcrest, IP8M-T2499EW 3840x2160, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp','NULL',554,'rtsp://:@/cam/realmonitor?channel=1&subtype=0',NULL,3840,2160,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 320x240, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&req_fps=5',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 640x480, mpjpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 640x480, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&req_fps=5',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 640x480, jpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 320x240, mpjpeg, B&W','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&color=0',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 640x480, mpjpeg, B&W','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&color=0',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240',NULL,320,240,3,NULL,1,4,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 320x240, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&req_fps=5',NULL,320,240,3,NULL,1,4,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,NULL,1,4,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,5.0,1,4,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 640x480, mpjpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480',NULL,640,480,3,NULL,1,4,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 640x480, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&req_fps=5',NULL,640,480,3,NULL,1,4,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 640x480, jpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,NULL,1,4,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,5.0,1,4,NULL,':',100,100); +INSERT into MonitorPresets VALUES (NULL,NULL,'Axis IP, mpeg4, unicast','Remote','rtsp',0,255,'rtsp','rtpUni','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT into MonitorPresets VALUES (NULL,NULL,'Axis IP, mpeg4, multicast','Remote','rtsp',0,255,'rtsp','rtpMulti','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT into MonitorPresets VALUES (NULL,NULL,'Axis IP, mpeg4, RTP/RTSP','Remote','rtsp',0,255,'rtsp','rtpRtsp','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT into MonitorPresets VALUES (NULL,NULL,'Axis IP, mpeg4, RTP/RTSP/HTTP','Remote',NULL,NULL,NULL,'rtsp','rtpRtspHttp','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'D-link DCS-930L, 640x480, mjpeg','Remote','http',0,0,'http','simple','',80,'/mjpeg.cgi',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'D-Link DCS-5020L, 640x480, mjpeg','Remote','http',0,0,'http','simple',':@','80','/video.cgi',NULL,640,480,0,NULL,1,'34',NULL,':@',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 640x480, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 640x480, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,1,5,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,1,5,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,1,5,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 640x480, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,1,5,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 640x480, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,1,5,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,5.0,1,5,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Gadspot IP, jpeg','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Gadspot IP, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','',80,'/GetData.cgi',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'IP Webcam by Pavel Khlebovich 1920x1080','Remote','/dev/video','0',255,'http','simple','','8080','/video','',1920,1080,0,NULL,0,'0','','',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'VEO Observer, jpeg','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Blue Net Video Server, jpeg','Remote','http',0,0,'http','simple','',80,'/cgi-bin/image.cgi?control=0&id=admin&passwd=admin',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT into MonitorPresets VALUES (NULL,NULL,'ACTi IP, mpeg4, unicast','Remote',NULL,NULL,NULL,'rtsp','rtpUni','',7070,'','/track',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp:///axis-media/media.amp?videocodec=h264',NULL,NULL,NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Vivotek FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp://:554/live.sdp',NULL,NULL,NULL,352,240,NULL,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp:///axis-media/media.amp',NULL,NULL,NULL,640,480,NULL,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'ACTi TCM FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp://admin:123456@:7070',NULL,NULL,NULL,320,240,NULL,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), PAL, 320x240','Local','/dev/video',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), PAL, 320x240, max 5 FPS','Local','/dev/video',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), PAL, 640x480','Local','/dev/video',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), PAL, 640x480, max 5 FPS','Local','/dev/video',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), NTSC, 320x240','Local','/dev/video',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), NTSC, 320x240, max 5 FPS','Local','/dev/video',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), NTSC, 640x480','Local','/dev/video',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), NTSC, 640x480, max 5 FPS','Local','/dev/video',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), PAL, 320x240','Local','/dev/video',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), PAL, 320x240, max 5 FPS','Local','/dev/video',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), PAL, 640x480','Local','/dev/video',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), PAL, 640x480, max 5 FPS','Local','/dev/video',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), NTSC, 320x240','Local','/dev/video',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), NTSC, 320x240, max 5 FPS','Local','/dev/video',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), NTSC, 640x480','Local','/dev/video',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), NTSC, 640x480, max 5 FPS','Local','/dev/video',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Remote ZoneMinder','Remote',NULL,NULL,NULL,'http','simple','',80,'/cgi-bin/nph-zms?mode=jpeg&monitor=&scale=100&maxfps=5&buffer=0',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Foscam FI8620 FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://:@:554/11',NULL,704,576,0,NULL,1,'10','','',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Foscam FI8608W FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://:@:554/11',NULL,640,480,0,NULL,1,'11','','',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Foscam FI9821W FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://:@:88/videoMain',NULL,1280,720,0,NULL,1,'12','','',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Loftek Sentinel PTZ, 640x480, mjpeg','Remote','http',0,0,NULL,NULL,'','80','/videostream.cgi?user=&pwd=&resolution=32&rate=11',NULL,640,480,4,NULL,1,'13','',':@',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Airlink 777W PTZ, 640x480, mjpeg','Remote','http',0,0,NULL,NULL,':@','80','/cgi/mjpg/mjpg.cgi',NULL,640,480,4,NULL,1,'7','',':@',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'SunEyes SP-P1802SWPTZ','Libvlc','/dev/video','0',255,'','rtpMulti','','80','rtsp://:554/11','',1920,1080,0,0.00,1,'16','-speed=64',':',100,33); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Qihan IP, 1280x720, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp',NULL,554,'rtsp:///tcp_live/ch0_0',NULL,1280,720,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Qihan IP, 1920x1080, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp',NULL,554,'rtsp:///tcp_live/ch0_0',NULL,1920,1080,3,NULL,0,NULL,NULL,NULL,100,100); -- -- Add some zone preset values @@ -925,14 +1091,44 @@ 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, + access INT(10) UNSIGNED DEFAULT NULL, + data text, + PRIMARY KEY(id) +) ENGINE=InnoDB; + +CREATE TABLE Snapshots ( + `Id` int(10) unsigned NOT NULL auto_increment, + `Name` VARCHAR(64), + `Description` TEXT, + `CreatedBy` int(10), + `CreatedOn` datetime default NULL, + PRIMARY KEY(Id) +) ENGINE=InnoDB; + +CREATE TABLE Snapshot_Events ( + `Id` int(10) unsigned NOT NULL auto_increment, + `SnapshotId` int(10) unsigned NOT NULL, + FOREIGN KEY (`SnapshotId`) REFERENCES `Snapshots` (`Id`) ON DELETE CASCADE, + `EventId` bigint unsigned NOT NULL, + FOREIGN KEY (`EventId`) REFERENCES `Events` (`Id`) ON DELETE CASCADE, + KEY `Snapshot_Events_SnapshotId_idx` (`SnapshotId`), + PRIMARY KEY(Id) +) ENGINE=InnoDB; + -- We generally don't alter triggers, we drop and re-create them, so let's keep them in a separate file that we can just source in update scripts. source @PKGDATADIR@/db/triggers.sql + +source @PKGDATADIR@/db/manufacturers.sql +source @PKGDATADIR@/db/models.sql -- -- Apply the initial configuration -- diff --git a/db/zm_update-1.27.99.0.sql b/db/zm_update-1.27.99.0.sql deleted file mode 100644 index 5b5e495b6..000000000 --- a/db/zm_update-1.27.99.0.sql +++ /dev/null @@ -1,206 +0,0 @@ --- --- This updates a 1.27.0 database to 1.27.1 --- - --- --- Add Controls definition for Wanscam --- -INSERT INTO Controls -SELECT * FROM (SELECT NULL as Id, - 'WanscamPT' as Name, - 'Remote' as Type, - 'Wanscam' as Protocol, - 1 as CanWake, - 1 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, - 1 as CanIris, - 0 as CanAutoIris, - 1 as CanIrisAbs, - 0 as CanIrisRel, - 0 as CanIrisCon, - 0 as MinIrisRange, - 16 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, - 1 as CanWhite, - 0 as CanAutoWhite, - 1 as CanWhiteAbs, - 0 as CanWhiteRel, - 0 as CanWhiteCon, - 0 as MinWhiteRange, - 16 as MaxWhiteRange, - 0 as MinWhiteStep, - 0 as MaxWhiteStep, - 0 as HasWhiteSpeed, - 0 as MinWhiteSpeed, - 0 as MaxWhiteSpeed, - 1 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, - 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 = 'WanscamPT' -) LIMIT 1; - --- Add extend alarm frame count to zone definition and Presets -SET @s = (SELECT IF( - (SELECT COUNT(*) - FROM INFORMATION_SCHEMA.COLUMNS - WHERE table_name = 'Zones' - AND table_schema = DATABASE() - AND column_name = 'ExtendAlarmFrames' - ) > 0, -"SELECT 'Column ExtendAlarmFrames exists in Zones'", -"ALTER TABLE `Zones` ADD `ExtendAlarmFrames` smallint(5) unsigned not null default 0 AFTER `OverloadFrames`" -)); - -PREPARE stmt FROM @s; -EXECUTE stmt; - -SET @s = (SELECT IF( - (SELECT COUNT(*) - FROM INFORMATION_SCHEMA.COLUMNS - WHERE table_name = 'ZonePresets' - AND table_schema = DATABASE() - AND column_name = 'ExtendAlarmFrames' - ) > 0, -"SELECT 'Column ExtendAlarmFrames exists in ZonePresets'", -"ALTER TABLE `ZonePresets` ADD `ExtendAlarmFrames` smallint(5) unsigned not null default 0 AFTER `OverloadFrames`" -)); - -PREPARE stmt FROM @s; -EXECUTE stmt; - --- --- Add MotionSkipFrame field for controlling how many frames motion detection should skip. --- - -SET @s = (SELECT IF( - (SELECT COUNT(*) - FROM INFORMATION_SCHEMA.COLUMNS - WHERE table_name = 'Monitors' - AND table_schema = DATABASE() - AND column_name = 'MotionFrameSkip' - ) > 0, -"SELECT 1", -"ALTER TABLE `Monitors` ADD `MotionFrameSkip` smallint(5) unsigned NOT NULL default '0' AFTER `FrameSkip`" -)); - -PREPARE stmt FROM @s; -EXECUTE stmt; - --- --- Add Monitor Options field; used for specifying Ffmpeg AVoptions like rtsp_transport http or libVLC options --- -SET @s = (SELECT IF( - (SELECT COUNT(*) - FROM INFORMATION_SCHEMA.COLUMNS - WHERE table_name = 'Monitors' - AND table_schema = DATABASE() - AND column_name = 'Options' - ) > 0, -"SELECT 'Column Options already exists in Monitors'", -"ALTER TABLE `Monitors` ADD `Options` varchar(255) not null default '' AFTER `Path`" -)); - -PREPARE stmt FROM @s; -EXECUTE stmt; - --- --- Add V4LMultiBuffer and V4LCapturesPerFrame to Monitor --- - -SET @s = (SELECT IF( - (SELECT COUNT(*) - FROM INFORMATION_SCHEMA.COLUMNS - WHERE table_name = 'Monitors' - AND table_schema = DATABASE() - AND column_name = 'V4LMultiBuffer' - ) > 0, -"SELECT 'Column V4LMultiBuffer exists in Monitors'", -"ALTER TABLE `Monitors` ADD `V4LMultiBuffer` tinyint(1) unsigned AFTER `Format`" -)); - -PREPARE stmt FROM @s; -EXECUTE stmt; - -SET @s = (SELECT IF( - (SELECT COUNT(*) - FROM INFORMATION_SCHEMA.COLUMNS - WHERE table_name = 'Monitors' - AND table_schema = DATABASE() - AND column_name = 'V4LCapturesPerFrame' - ) > 0, -"SELECT 'Column V4LCapturesPerFrame exists in Monitors'", -"ALTER TABLE `Monitors` ADD `V4LCapturesPerFrame` tinyint(3) unsigned AFTER `V4LMultiBuffer`" -)); - -PREPARE stmt FROM @s; -EXECUTE stmt; 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.31.13.sql b/db/zm_update-1.31.13.sql index dd63b347a..150264027 100644 --- a/db/zm_update-1.31.13.sql +++ b/db/zm_update-1.31.13.sql @@ -13,6 +13,8 @@ SET @s = (SELECT IF( PREPARE stmt FROM @s; EXECUTE stmt; +UPDATE `Events` SET `SaveJPEGs`=(SELECT `SaveJPEGs` FROM `Monitors` WHERE Monitors.Id = MonitorId) WHERE `SaveJPEGs` IS NULL; + SET @s = (SELECT IF( (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() AND table_name = 'Storage' diff --git a/db/zm_update-1.31.38.sql b/db/zm_update-1.31.38.sql index 0ca0be6ea..12fd3e96d 100644 --- a/db/zm_update-1.31.38.sql +++ b/db/zm_update-1.31.38.sql @@ -176,8 +176,10 @@ BEGIN WHERE Id=OLD.MonitorId; END IF; END IF; - ELSEIF ( NEW.Archived AND diff ) THEN - UPDATE Events_Archived SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; + ELSE + IF ( NEW.Archived AND diff ) THEN + UPDATE Events_Archived SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; + END IF; END IF; IF ( diff ) THEN @@ -185,7 +187,6 @@ BEGIN END IF; END; - // delimiter ; diff --git a/db/zm_update-1.31.40.sql b/db/zm_update-1.31.40.sql index 79fc3836c..50ffed736 100644 --- a/db/zm_update-1.31.40.sql +++ b/db/zm_update-1.31.40.sql @@ -10,3 +10,18 @@ SET @s = (SELECT IF( PREPARE stmt FROM @s; EXECUTE stmt; + + +ALTER TABLE `Monitors` MODIFY `OutputCodec` INT UNSIGNED default 0; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'Encoder' + ) > 0, +"SELECT 'Column Encoder already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `Encoder` enum('auto','h264','h264_omx','mjpeg','mpeg1','mpeg2') AFTER `OutputCodec`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.31.5.sql b/db/zm_update-1.31.5.sql index 859487e88..d315c8e84 100644 --- a/db/zm_update-1.31.5.sql +++ b/db/zm_update-1.31.5.sql @@ -10,7 +10,7 @@ SET @s = (SELECT IF( AND column_name = 'ParentId' ) > 0, "SELECT 'Column GroupId exists in Groups'", -"ALTER TABLE Groups ADD `ParentId` int(10) unsigned AFTER `Name`" +"ALTER TABLE `Groups` ADD `ParentId` int(10) unsigned AFTER `Name`" )); PREPARE stmt FROM @s; diff --git a/db/zm_update-1.31.7.sql b/db/zm_update-1.31.7.sql index 1221d9adb..0afd76ce5 100644 --- a/db/zm_update-1.31.7.sql +++ b/db/zm_update-1.31.7.sql @@ -3,7 +3,7 @@ SET @s = (SELECT IF( AND table_name = 'Groups' AND column_name = 'MonitorIds' ) > 0, - "ALTER TABLE Groups MODIFY `MonitorIds` text NOT NULL", + "ALTER TABLE `Groups` MODIFY `MonitorIds` text NOT NULL", "SELECT 'Groups no longer has MonitorIds'" )); diff --git a/db/zm_update-1.34.0.sql b/db/zm_update-1.34.0.sql new file mode 100644 index 000000000..b8bd3e4ed --- /dev/null +++ b/db/zm_update-1.34.0.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.33.16 database to 1.34.0 +-- +-- No changes required +-- diff --git a/db/zm_update-1.34.1.sql b/db/zm_update-1.34.1.sql new file mode 100644 index 000000000..65ba2e5ff --- /dev/null +++ b/db/zm_update-1.34.1.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.0 database to 1.34.1 +-- +-- No changes required +-- diff --git a/db/zm_update-1.34.2.sql b/db/zm_update-1.34.2.sql new file mode 100644 index 000000000..1fcc882d2 --- /dev/null +++ b/db/zm_update-1.34.2.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.1 database to 1.34.2 +-- +-- No changes required +-- diff --git a/db/zm_update-1.34.20.sql b/db/zm_update-1.34.20.sql new file mode 100644 index 000000000..3512534be --- /dev/null +++ b/db/zm_update-1.34.20.sql @@ -0,0 +1,2 @@ +/* This was done in 1.31.0 but zm_create.sql.in wasn't updated to match. */ +ALTER TABLE Monitors MODIFY LinkedMonitors varchar(255); diff --git a/db/zm_update-1.34.3.sql b/db/zm_update-1.34.3.sql new file mode 100644 index 000000000..b84207047 --- /dev/null +++ b/db/zm_update-1.34.3.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.2 database to 1.34.3 +-- +-- No changes required +-- diff --git a/db/zm_update-1.34.4.sql b/db/zm_update-1.34.4.sql new file mode 100644 index 000000000..2910943a0 --- /dev/null +++ b/db/zm_update-1.34.4.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.3 database to 1.34.4 +-- +-- No changes required +-- diff --git a/db/zm_update-1.34.5.sql b/db/zm_update-1.34.5.sql new file mode 100644 index 000000000..7e12b977f --- /dev/null +++ b/db/zm_update-1.34.5.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.4 database to 1.34.5 +-- +-- No changes required +-- diff --git a/db/zm_update-1.34.6.sql b/db/zm_update-1.34.6.sql new file mode 100644 index 000000000..1a58bee1f --- /dev/null +++ b/db/zm_update-1.34.6.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.5 database to 1.34.6 +-- +-- No changes required +-- diff --git a/db/zm_update-1.34.7.sql b/db/zm_update-1.34.7.sql new file mode 100644 index 000000000..ba86b1202 --- /dev/null +++ b/db/zm_update-1.34.7.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.6 database to 1.34.7 +-- +-- No changes required +-- diff --git a/db/zm_update-1.35.0.sql b/db/zm_update-1.35.0.sql new file mode 100644 index 000000000..2e9132a63 --- /dev/null +++ b/db/zm_update-1.35.0.sql @@ -0,0 +1,41 @@ +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'EmailTo' + ) > 0, +"SELECT 'Column EmailTo already exists in Filters'", +"ALTER TABLE `Filters` ADD `EmailTo` TEXT AFTER `AutoEmail`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +UPDATE Filters SET EmailTo=(SELECT Value FROM Config WHERE Name='ZM_EMAIL_ADDRESS') WHERE AutoEmail=1; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'EmailSubject' + ) > 0, +"SELECT 'Column EmailSubject already exists in Filters'", +"ALTER TABLE `Filters` ADD `EmailSubject` TEXT AFTER `EmailTo`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +UPDATE Filters SET EmailSubject=(SELECT Value FROM Config WHERE Name='ZM_EMAIL_SUBJECT') WHERE AutoEmail=1; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'EmailBody' + ) > 0, +"SELECT 'Column EmailBody already exists in Filters'", +"ALTER TABLE `Filters` ADD `EmailBody` TEXT AFTER `EmailSubject`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +UPDATE Filters SET EmailBody=(SELECT Value FROM Config WHERE Name='ZM_EMAIL_BODY') WHERE AutoEmail=1; diff --git a/db/zm_update-1.35.1.sql b/db/zm_update-1.35.1.sql new file mode 100644 index 000000000..d250cf751 --- /dev/null +++ b/db/zm_update-1.35.1.sql @@ -0,0 +1,12 @@ + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Storage' + AND column_name = 'Enabled' + ) > 0, +"SELECT 'Column Enabled already exists in Storage'", +"ALTER TABLE `Storage` ADD `Enabled` BOOLEAN NOT NULL default true AFTER `DoDelete`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.10.sql b/db/zm_update-1.35.10.sql new file mode 100644 index 000000000..81c768b57 --- /dev/null +++ b/db/zm_update-1.35.10.sql @@ -0,0 +1,15 @@ +-- +-- Add AutoUnarchive action to Filters +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'AutoUnarchive' + ) > 0, +"SELECT 'Column AutoUunarchive already exists in Filters'", +"ALTER TABLE Filters ADD `AutoUnarchive` tinyint(3) unsigned NOT NULL default '0' AFTER `AutoArchive`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.11.sql b/db/zm_update-1.35.11.sql new file mode 100644 index 000000000..5e4338d76 --- /dev/null +++ b/db/zm_update-1.35.11.sql @@ -0,0 +1,92 @@ +/* Change Id type to BIGINT. */ +set @exist := (SELECT count(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'Events' AND COLUMN_NAME = 'Id' and DATA_TYPE='bigint'); + +set @sqlstmt := if( @exist = 0, "ALTER TABLE Events MODIFY Id bigint unsigned NOT NULL auto_increment", "SELECT 'Events.Id is already BIGINT'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +/* Add FOREIGN KEYS After deleting lost records */ +set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Frames' and column_name='EventId' and referenced_table_name='Events' and referenced_column_name='Id'); + +set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'"); +set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY EventId in Frames already exists'", @sqlstmt); +set @sqlstmt := if( @exist = 0, "SELECT 'Adding foreign key for EventId to Frames'", @sqlstmt); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "SELECT 'Deleting unlinked Frames'", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "DELETE FROM Frames WHERE EventId NOT IN (SELECT Id FROM Events)", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "ALTER TABLE Frames ADD FOREIGN KEY (EventId) REFERENCES Events (Id) ON DELETE CASCADE", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + + +set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Stats' and column_name='EventId' and referenced_table_name='Events' and referenced_column_name='Id'); +set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'"); +set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY EventId in Stats already exists'", @sqlstmt); +set @sqlstmt := if( @exist = 0, "SELECT 'Adding FOREIGN KEY for EventId to Stats'", @sqlstmt); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "SELECT 'Deleting unlinked Stats'", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "DELETE FROM Stats WHERE EventId NOT IN (SELECT Id FROM Events);", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "ALTER TABLE Stats ADD FOREIGN KEY (EventId) REFERENCES Events (Id) ON DELETE CASCADE", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + + +set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Stats' and column_name='MonitorId' and referenced_table_name='Monitors' and referenced_column_name='Id'); +set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'"); +set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY for MonitorId in Stats already exists'", @sql_stmt); +set @sqlstmt := if( @exist = 0, "SELECT 'Adding FOREIGN KEY for MonitorId to Stats'", @sqlstmt); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "SELECT 'Deleting unlinked Stats'", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "DELETE FROM Stats WHERE MonitorId NOT IN (SELECT Id FROM Monitors);", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "ALTER TABLE Stats ADD FOREIGN KEY (MonitorId) REFERENCES Monitors (Id) ON DELETE CASCADE", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Stats' and column_name='ZoneId' and referenced_table_name='Zones' and referenced_column_name='Id'); +set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'"); +set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY for ZoneId in Stats already exists'", @sqlstmt); +set @sqlstmt := if( @exist = 0, "SELECT 'Adding foreign key for ZoneId to Stats'", @sqlstmt); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "SELECT 'Deleting unlinked Stats'", "SELECT 'Ok'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "DELETE FROM Stats WHERE ZoneId NOT IN (SELECT Id FROM Zones);", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "ALTER TABLE Stats ADD FOREIGN KEY (ZoneId) REFERENCES Zones (Id) ON DELETE CASCADE", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +SELECT 'Adding foreign key for MonitorId to Zones'; +set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Zones' and column_name='MonitorId' and referenced_table_name='Monitors' and referenced_column_name='Id'); +set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'"); +set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY for MonitorId in Zones already exists'", @sqlstmnt); +set @sqlstmt := if( @exist = 0, "SELECT 'Adding foreign key for MonitorId in Zones'", @sqlstmnt); + +/*"SELECT 'FOREIGN KEY for MonitorId in Zones does not already exist'");*/ +set @badzones := (select count(*) FROM Zones WHERE MonitorId NOT IN (SELECT Id FROM Monitors)); +set @sqlstmt := if ( @badzones > 0, "SELECT 'You have Zones with no Monitor record in the Monitors table. Please delete them manually'", "ALTER TABLE Zones ADD FOREIGN KEY (MonitorId) REFERENCES Monitors (Id)"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; diff --git a/db/zm_update-1.35.12.sql b/db/zm_update-1.35.12.sql new file mode 100644 index 000000000..b8c54f713 --- /dev/null +++ b/db/zm_update-1.35.12.sql @@ -0,0 +1,18 @@ +-- +-- Update Filters table to have a LockRows Column +-- + +SELECT 'Checking for LockRows in Filters'; +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Filters' + AND table_schema = DATABASE() + AND column_name = 'LockRows' + ) > 0, +"SELECT 'Column LockRows already exists in Filters'", +"ALTER TABLE Filters ADD COLUMN `LockRows` tinyint(1) unsigned NOT NULL default '0' AFTER `Concurrent`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.13.sql b/db/zm_update-1.35.13.sql new file mode 100644 index 000000000..2911aaf65 --- /dev/null +++ b/db/zm_update-1.35.13.sql @@ -0,0 +1,101 @@ +/* DateTime is invalid and it being set here will cause warnings because it isn't in the dropdown set of values in Filter edit. */ +UPDATE Config SET Value='StartDateTime' WHERE Name='ZM_WEB_EVENT_SORT_FIELD' AND Value='DateTime'; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Events' + AND column_name = 'StartDateTime' + ) > 0, +"SELECT 'Column StartDateTime already exists in Events'", +"ALTER TABLE Events CHANGE StartTime StartDateTime datetime default NULL" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Events' + AND column_name = 'EndDateTime' + ) > 0, + "SELECT 'Column EndDateTime already exists in Events'", + "ALTER TABLE Events CHANGE EndTime EndDateTime datetime default NULL" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Events_Hour' + AND column_name = 'StartDateTime' + ) > 0, + "SELECT 'Column StartDateTime already exists in Events_Hour'", + "ALTER TABLE Events_Hour CHANGE StartTime StartDateTime datetime default NULL" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Events_Day' + AND column_name = 'StartDateTime' + ) > 0, + "SELECT 'Column StartDateTime already exists in Events_Day'", + "ALTER TABLE Events_Day CHANGE StartTime StartDateTime datetime default NULL" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Events_Week' + AND column_name = 'StartDateTime' + ) > 0, + "SELECT 'Column StartDateTime already exists in Events_Week'", + "ALTER TABLE Events_Week CHANGE StartTime StartDateTime datetime default NULL" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Events_Month' + AND column_name = 'StartDateTime' + ) > 0, + "SELECT 'Column StartDateTime already exists in Events_Month'", + "ALTER TABLE Events_Month CHANGE StartTime StartDateTime datetime default NULL" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +delimiter // + +DROP TRIGGER IF EXISTS event_insert_trigger// + +/* The assumption is that when an Event is inserted, it has no size yet, so don't bother updating the DiskSpace, just the count. + * The DiskSpace will get update in the Event Update Trigger + */ +CREATE TRIGGER event_insert_trigger AFTER INSERT ON Events +FOR EACH ROW + BEGIN + + INSERT INTO Events_Hour (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Day (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Week (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Month (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + UPDATE Monitors SET + HourEvents = COALESCE(HourEvents,0)+1, + DayEvents = COALESCE(DayEvents,0)+1, + WeekEvents = COALESCE(WeekEvents,0)+1, + MonthEvents = COALESCE(MonthEvents,0)+1, + TotalEvents = COALESCE(TotalEvents,0)+1 + WHERE Id=NEW.MonitorId; +END; +// + +delimiter ; diff --git a/db/zm_update-1.35.14.sql b/db/zm_update-1.35.14.sql new file mode 100644 index 000000000..f3c8bf779 --- /dev/null +++ b/db/zm_update-1.35.14.sql @@ -0,0 +1,574 @@ +SELECT 'This update may make changes that require SUPER privileges. If you see an error message saying: + +ERROR 1419 (HY000) at line 298: You do not have the SUPER privilege and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable) + +You will have to either run this update as root manually using something like (on ubuntu/debian) + +sudo mysql --defaults-file=/etc/mysql/debian.cnf zm < /usr/share/zoneminder/db/zm_update-1.35.14.sql + +OR + +sudo mysql --defaults-file=/etc/mysql/debian.cnf "set global log_bin_trust_function_creators=1;" +sudo zmupdate.pl +'; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'TotalEvents' + ) > 0, +"SELECT 'Column TotalEvents already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `TotalEvents` INT(10) AFTER `CaptureBandwidth`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'TotalEvents' + ) > 0, +"ALTER TABLE `Monitors` DROP `TotalEvents`", +"SELECT 'Column TotalEvents is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'TotalEventDiskSpace' + ) > 0, +"SELECT 'Column TotalEventDiskSpace already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `TotalEventDiskSpace` BIGINT AFTER `TotalEvents`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'TotalEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitors` DROP `TotalEventDiskSpace`", +"SELECT 'Column TotalEventDiskSpace is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'HourEvents' + ) > 0, +"SELECT 'Column HourEvents already exists in Monitors'", +"ALTER TABLE `Monitor_Status` ADD `HourEvents` INT(10) AFTER `TotalEventDiskSpace`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'HourEvents' + ) > 0, +"ALTER TABLE `Monitors` DROP `HourEvents`", +"SELECT 'Column HourEvents is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'HourEventDiskSpace' + ) > 0, +"SELECT 'Column HourEventDiskSpace already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `HourEventDiskSpace` BIGINT AFTER `HourEvents`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'HourEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitors` DROP `HourEventDiskSpace`", +"SELECT 'Column HourEventDiskSpace is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'DayEvents' + ) > 0, +"SELECT 'Column DayEvents already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `DayEvents` INT(10) AFTER `HourEventDiskSpace`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'DayEvents' + ) > 0, +"ALTER TABLE `Monitors` DROP `DayEvents`", +"SELECT 'Column DayEvents is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'DayEventDiskSpace' + ) > 0, +"SELECT 'Column DayEventDiskSpace already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `DayEventDiskSpace` BIGINT AFTER `DayEvents`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'DayEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitors` DROP `DayEventDiskSpace`", +"SELECT 'Column DayEventDiskSpace is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'WeekEvents' + ) > 0, +"SELECT 'Column WeekEvents already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `WeekEvents` INT(10) AFTER `DayEventDiskSpace`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'WeekEvents' + ) > 0, +"ALTER TABLE `Monitors` DROP `WeekEvents`", +"SELECT 'Column WeekEvents is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'WeekEventDiskSpace' + ) > 0, +"SELECT 'Column WeekEventDiskSpace already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `WeekEventDiskSpace` BIGINT AFTER `WeekEvents`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'WeekEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitors` DROP `WeekEventDiskSpace`", +"SELECT 'Column WeekEventDiskSpace is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'MonthEvents' + ) > 0, +"SELECT 'Column MonthEvents already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `MonthEvents` INT(10) AFTER `WeekEventDiskSpace`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'MonthEvents' + ) > 0, +"ALTER TABLE `Monitors` DROP `MonthEvents`", +"SELECT 'Column MonthEvents is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'MonthEventDiskSpace' + ) > 0, +"SELECT 'Column MonthEventDiskSpace already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `MonthEventDiskSpace` BIGINT AFTER `MonthEvents`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'MonthEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitors` DROP `MonthEventDiskSpace`", +"SELECT 'Column MonthEventDiskSpace is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'ArchivedEvents' + ) > 0, +"SELECT 'Column ArchivedEvents already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `ArchivedEvents` INT(10) AFTER `MonthEventDiskSpace`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ArchivedEvents' + ) > 0, +"ALTER TABLE `Monitors` DROP `ArchivedEvents`", +"SELECT 'Column ArchivedEvents is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'ArchivedEventDiskSpace' + ) > 0, +"SELECT 'Column ArchivedEventDiskSpace already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `ArchivedEventDiskSpace` BIGINT AFTER `ArchivedEvents`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ArchivedEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitors` DROP `ArchivedEventDiskSpace`", +"SELECT 'Column ArchivedEventDiskSpace is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +UPDATE Monitor_Status INNER JOIN ( + SELECT MonitorId, + COUNT(Id) AS TotalEvents, + SUM(DiskSpace) AS TotalEventDiskSpace, + SUM(IF(Archived,1,0)) AS ArchivedEvents, + SUM(IF(Archived,DiskSpace,0)) AS ArchivedEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 hour),1,0)) AS HourEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 hour),DiskSpace,0)) AS HourEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 day),1,0)) AS DayEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 day),DiskSpace,0)) AS DayEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 week),1,0)) AS WeekEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 week),DiskSpace,0)) AS WeekEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 month),1,0)) AS MonthEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 month),DiskSpace,0)) AS MonthEventDiskSpace + FROM Events GROUP BY MonitorId + ) AS E ON E.MonitorId=Monitor_Status.MonitorId SET + Monitor_Status.TotalEvents = E.TotalEvents, + Monitor_Status.TotalEventDiskSpace = E.TotalEventDiskSpace, + Monitor_Status.ArchivedEvents = E.ArchivedEvents, + Monitor_Status.ArchivedEventDiskSpace = E.ArchivedEventDiskSpace, + Monitor_Status.HourEvents = E.HourEvents, + Monitor_Status.HourEventDiskSpace = E.HourEventDiskSpace, + Monitor_Status.DayEvents = E.DayEvents, + Monitor_Status.DayEventDiskSpace = E.DayEventDiskSpace, + Monitor_Status.WeekEvents = E.WeekEvents, + Monitor_Status.WeekEventDiskSpace = E.WeekEventDiskSpace, + Monitor_Status.MonthEvents = E.MonthEvents, + Monitor_Status.MonthEventDiskSpace = E.MonthEventDiskSpace; + + +delimiter // +DROP TRIGGER IF EXISTS Events_Hour_delete_trigger// +CREATE TRIGGER Events_Hour_delete_trigger BEFORE DELETE ON Events_Hour +FOR EACH ROW BEGIN + UPDATE Monitor_Status SET + HourEvents = GREATEST(COALESCE(HourEvents,1)-1,0), + HourEventDiskSpace=GREATEST(COALESCE(HourEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; +END; +// + +DROP TRIGGER IF EXISTS Events_Hour_update_trigger// + +CREATE TRIGGER Events_Hour_update_trigger AFTER UPDATE ON Events_Hour +FOR EACH ROW + BEGIN + declare diff BIGINT default 0; + + set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); + IF ( diff ) THEN + IF ( NEW.MonitorID != OLD.MonitorID ) THEN + UPDATE Monitor_Status SET HourEventDiskSpace=GREATEST(COALESCE(HourEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Monitor_Status.MonitorId=OLD.MonitorId; + UPDATE Monitor_Status SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Monitor_Status.MonitorId=NEW.MonitorId; + ELSE + UPDATE Monitor_Status SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)+diff WHERE Monitor_Status.MonitorId=NEW.MonitorId; + END IF; + END IF; + END; +// + +DROP TRIGGER IF EXISTS Events_Day_delete_trigger// +CREATE TRIGGER Events_Day_delete_trigger BEFORE DELETE ON Events_Day +FOR EACH ROW BEGIN + UPDATE Monitor_Status SET + DayEvents = GREATEST(COALESCE(DayEvents,1)-1,0), + DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; +END; +// + +DROP TRIGGER IF EXISTS Events_Day_update_trigger; +CREATE TRIGGER Events_Day_update_trigger AFTER UPDATE ON Events_Day +FOR EACH ROW + BEGIN + declare diff BIGINT default 0; + + set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); + IF ( diff ) THEN + IF ( NEW.MonitorID != OLD.MonitorID ) THEN + UPDATE Monitor_Status SET DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Monitor_Status.MonitorId=OLD.MonitorId; + UPDATE Monitor_Status SET DayEventDiskSpace=COALESCE(DayEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Monitor_Status.MonitorId=NEW.MonitorId; + ELSE + UPDATE Monitor_Status SET DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)+diff,0) WHERE Monitor_Status.MonitorId=NEW.MonitorId; + END IF; + END IF; + END; + // + + +DROP TRIGGER IF EXISTS Events_Week_delete_trigger// +CREATE TRIGGER Events_Week_delete_trigger BEFORE DELETE ON Events_Week +FOR EACH ROW BEGIN + UPDATE Monitor_Status SET + WeekEvents = GREATEST(COALESCE(WeekEvents,1)-1,0), + WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; +END; +// + +DROP TRIGGER IF EXISTS Events_Week_update_trigger; +CREATE TRIGGER Events_Week_update_trigger AFTER UPDATE ON Events_Week +FOR EACH ROW + BEGIN + declare diff BIGINT default 0; + + set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); + IF ( diff ) THEN + IF ( NEW.MonitorID != OLD.MonitorID ) THEN + UPDATE Monitor_Status SET WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Monitor_Status.MonitorId=OLD.MonitorId; + UPDATE Monitor_Status SET WeekEventDiskSpace=COALESCE(WeekEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Monitor_Status.MonitorId=NEW.MonitorId; + ELSE + UPDATE Monitor_Status SET WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)+diff,0) WHERE Monitor_Status.MonitorId=NEW.MonitorId; + END IF; + END IF; + END; + // + +DROP TRIGGER IF EXISTS Events_Month_delete_trigger// +CREATE TRIGGER Events_Month_delete_trigger BEFORE DELETE ON Events_Month +FOR EACH ROW BEGIN + UPDATE Monitor_Status SET + MonthEvents = GREATEST(COALESCE(MonthEvents,1)-1,0), + MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; +END; +// + +DROP TRIGGER IF EXISTS Events_Month_update_trigger; +CREATE TRIGGER Events_Month_update_trigger AFTER UPDATE ON Events_Month +FOR EACH ROW + BEGIN + declare diff BIGINT default 0; + + set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); + IF ( diff ) THEN + IF ( NEW.MonitorID != OLD.MonitorID ) THEN + UPDATE Monitor_Status SET MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)-COALESCE(OLD.DiskSpace),0) WHERE Monitor_Status.MonitorId=OLD.MonitorId; + UPDATE Monitor_Status SET MonthEventDiskSpace=COALESCE(MonthEventDiskSpace,0)+COALESCE(NEW.DiskSpace) WHERE Monitor_Status.MonitorId=NEW.MonitorId; + ELSE + UPDATE Monitor_Status SET MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)+diff,0) WHERE Monitor_Status.MonitorId=NEW.MonitorId; + END IF; + END IF; + END; + // + +drop procedure if exists update_storage_stats// + +drop trigger if exists event_update_trigger// + +CREATE TRIGGER event_update_trigger AFTER UPDATE ON Events +FOR EACH ROW +BEGIN + declare diff BIGINT default 0; + + set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); + IF ( NEW.StorageId = OLD.StorageID ) THEN + IF ( diff ) THEN + UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) + diff,0) WHERE Storage.Id = OLD.StorageId; + END IF; + ELSE + IF ( NEW.DiskSpace ) THEN + UPDATE Storage SET DiskSpace = COALESCE(DiskSpace,0) + NEW.DiskSpace WHERE Storage.Id = NEW.StorageId; + END IF; + IF ( OLD.DiskSpace ) THEN + UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) - OLD.DiskSpace,0) WHERE Storage.Id = OLD.StorageId; + END IF; + END IF; + + UPDATE Events_Hour SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; + UPDATE Events_Day SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; + UPDATE Events_Week SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; + UPDATE Events_Month SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; + + IF ( NEW.Archived != OLD.Archived ) THEN + IF ( NEW.Archived ) THEN + INSERT INTO Events_Archived (EventId,MonitorId,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.DiskSpace); + UPDATE Monitor_Status SET ArchivedEvents = COALESCE(ArchivedEvents,0)+1, ArchivedEventDiskSpace = COALESCE(ArchivedEventDiskSpace,0) + COALESCE(NEW.DiskSpace,0) WHERE Monitor_Status.MonitorId=NEW.MonitorId; + ELSEIF ( OLD.Archived ) THEN + DELETE FROM Events_Archived WHERE EventId=OLD.Id; + UPDATE Monitor_Status + SET + ArchivedEvents = GREATEST(COALESCE(ArchivedEvents,0)-1,0), + ArchivedEventDiskSpace = GREATEST(COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; + ELSE + IF ( OLD.DiskSpace != NEW.DiskSpace ) THEN + UPDATE Events_Archived SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; + UPDATE Monitor_Status SET + ArchivedEventDiskSpace = GREATEST(COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0) + COALESCE(NEW.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; + END IF; + END IF; + ELSEIF ( NEW.Archived AND diff ) THEN + UPDATE Events_Archived SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; + END IF; + + IF ( diff ) THEN + UPDATE Monitor_Status + SET + TotalEventDiskSpace = GREATEST(COALESCE(TotalEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0) + COALESCE(NEW.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; + END IF; + +END; + +// + +DROP TRIGGER IF EXISTS event_insert_trigger// + +/* The assumption is that when an Event is inserted, it has no size yet, so don't bother updating the DiskSpace, just the count. + * The DiskSpace will get update in the Event Update Trigger + */ +CREATE TRIGGER event_insert_trigger AFTER INSERT ON Events +FOR EACH ROW + BEGIN + + INSERT INTO Events_Hour (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Day (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Week (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Month (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + UPDATE Monitor_Status SET + HourEvents = COALESCE(HourEvents,0)+1, + DayEvents = COALESCE(DayEvents,0)+1, + WeekEvents = COALESCE(WeekEvents,0)+1, + MonthEvents = COALESCE(MonthEvents,0)+1, + TotalEvents = COALESCE(TotalEvents,0)+1 + WHERE Monitor_Status.MonitorId=NEW.MonitorId; +END; +// + +DROP TRIGGER IF EXISTS event_delete_trigger// + +CREATE TRIGGER event_delete_trigger BEFORE DELETE ON Events +FOR EACH ROW +BEGIN + IF ( OLD.DiskSpace ) THEN + UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) - COALESCE(OLD.DiskSpace,0),0) WHERE Storage.Id = OLD.StorageId; + END IF; + DELETE FROM Events_Hour WHERE EventId=OLD.Id; + DELETE FROM Events_Day WHERE EventId=OLD.Id; + DELETE FROM Events_Week WHERE EventId=OLD.Id; + DELETE FROM Events_Month WHERE EventId=OLD.Id; + IF ( OLD.Archived ) THEN + DELETE FROM Events_Archived WHERE EventId=OLD.Id; + UPDATE Monitor_Status SET + ArchivedEvents = GREATEST(COALESCE(ArchivedEvents,1) - 1,0), + ArchivedEventDiskSpace = GREATEST(COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0),0), + TotalEvents = GREATEST(COALESCE(TotalEvents,1) - 1,0), + TotalEventDiskSpace = GREATEST(COALESCE(TotalEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; + ELSE + UPDATE Monitor_Status SET + TotalEvents = GREATEST(COALESCE(TotalEvents,1)-1,0), + TotalEventDiskSpace=GREATEST(COALESCE(TotalEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; + END IF; +END; + +// + +DELIMITER ; + +REPLACE INTO Events_Hour SELECT Id,MonitorId,StartDateTime,DiskSpace FROM Events WHERE StartDateTime > DATE_SUB(NOW(), INTERVAL 1 hour); +REPLACE INTO Events_Day SELECT Id,MonitorId,StartDateTime,DiskSpace FROM Events WHERE StartDateTime > DATE_SUB(NOW(), INTERVAL 1 day); +REPLACE INTO Events_Week SELECT Id,MonitorId,StartDateTime,DiskSpace FROM Events WHERE StartDateTime > DATE_SUB(NOW(), INTERVAL 1 week); +REPLACE INTO Events_Month SELECT Id,MonitorId,StartDateTime,DiskSpace FROM Events WHERE StartDateTime > DATE_SUB(NOW(), INTERVAL 1 month); +REPLACE INTO Events_Archived SELECT Id,MonitorId,DiskSpace FROM Events WHERE Archived=1; + +UPDATE Monitor_Status INNER JOIN ( + SELECT MonitorId, + COUNT(Id) AS TotalEvents, + SUM(DiskSpace) AS TotalEventDiskSpace, + SUM(IF(Archived,1,0)) AS ArchivedEvents, + SUM(IF(Archived,DiskSpace,0)) AS ArchivedEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 hour),1,0)) AS HourEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 hour),DiskSpace,0)) AS HourEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 day),1,0)) AS DayEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 day),DiskSpace,0)) AS DayEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 week),1,0)) AS WeekEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 week),DiskSpace,0)) AS WeekEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 month),1,0)) AS MonthEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 month),DiskSpace,0)) AS MonthEventDiskSpace + FROM Events GROUP BY MonitorId + ) AS E ON E.MonitorId=Monitor_Status.MonitorId SET + Monitor_Status.TotalEvents = E.TotalEvents, + Monitor_Status.TotalEventDiskSpace = E.TotalEventDiskSpace, + Monitor_Status.ArchivedEvents = E.ArchivedEvents, + Monitor_Status.ArchivedEventDiskSpace = E.ArchivedEventDiskSpace, + Monitor_Status.HourEvents = E.HourEvents, + Monitor_Status.HourEventDiskSpace = E.HourEventDiskSpace, + Monitor_Status.DayEvents = E.DayEvents, + Monitor_Status.DayEventDiskSpace = E.DayEventDiskSpace, + Monitor_Status.WeekEvents = E.WeekEvents, + Monitor_Status.WeekEventDiskSpace = E.WeekEventDiskSpace, + Monitor_Status.MonthEvents = E.MonthEvents, + Monitor_Status.MonthEventDiskSpace = E.MonthEventDiskSpace; diff --git a/db/zm_update-1.35.15.sql b/db/zm_update-1.35.15.sql new file mode 100644 index 000000000..65f4e5426 --- /dev/null +++ b/db/zm_update-1.35.15.sql @@ -0,0 +1,70 @@ +/* +Add the EndTime IS NOT NULL term to the Update Disk Space Filter. +This will only work if they havn't modified the stock filter + */ +UPDATE Filters SET Query_json='{"terms":[{"attr":"DiskSpace","op":"IS","val":"NULL"},{"cnj":"and","obr":"0","attr":"EndDateTime","op":"IS NOT","val":"NULL","cbr":"0"}]}' WHERE Query_json='{"terms":[{"attr":"DiskSpace","op":"IS","val":"NULL"}]}'; + +/* +Add the EndTime IS NOT NULL term to the Purge When Full Filter. +This will only work if they havn't modified the stock filter . +This is important to prevent SQL Errors inserting into Frames table if PurgeWhenFull deletes in-progress events. + */ +UPDATE Filters SET Query_json='{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="},{"cnj":"and","obr":"0","attr":"EndDateTime","op":"IS NOT","val":"NULL","cbr":"0"}],"limit":100,"sort_asc":1}' WHERE Query_json='{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="}],"limit":100,"sort_asc":1}'; + +/* Add FOREIGN KEYS After deleting lost records */ +set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Groups_Monitors' and column_name='GroupId' and referenced_table_name='Groups' and referenced_column_name='Id'); + +set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'"); +set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY GroupId in Groups_Monitors already exists'", @sqlstmt); +set @sqlstmt := if( @exist = 0, "SELECT 'Adding foreign key for GroupId to Groups_Monitors'", @sqlstmt); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "SELECT 'Deleting unlinked Groups_Monitors'", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "DELETE FROM `Groups_Monitors` WHERE `GroupId` NOT IN (SELECT `Id` FROM `Groups`)", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "ALTER TABLE `Groups_Monitors` ADD FOREIGN KEY (`GroupId`) REFERENCES `Groups` (`Id`) ON DELETE CASCADE", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +/* Add FOREIGN KEYS After deleting lost records */ +set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Groups_Monitors' and column_name='MonitorId' and referenced_table_name='Monitors' and referenced_column_name='Id'); + +set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'"); +set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY MonitorId in Groups_Monitors already exists'", @sqlstmt); +set @sqlstmt := if( @exist = 0, "SELECT 'Adding foreign key for MonitorId to Groups_Monitors'", @sqlstmt); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "SELECT 'Deleting unlinked Groups_Monitors'", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "DELETE FROM `Groups_Monitors` WHERE `MonitorId` NOT IN (SELECT `Id` FROM `Monitors`)", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "ALTER TABLE `Groups_Monitors` ADD FOREIGN KEY (`MonitorId`) REFERENCES `Monitors` (`Id`) ON DELETE CASCADE", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +/* Add FOREIGN KEYS After deleting lost records */ +set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Groups' and column_name='ParentId' and referenced_table_name='Groups' and referenced_column_name='Id'); + +set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'"); +set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY ParentId in Groups already exists'", @sqlstmt); +set @sqlstmt := if( @exist = 0, "SELECT 'Adding foreign key for ParentId to Groups'", @sqlstmt); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "SELECT 'Deleting unlinked Groups'", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "UPDATE `Groups` SET `ParentId` = NULL WHERE (ParentId IS NOT NULL) and ParentId NOT IN (SELECT * FROM(SELECT Id FROM `Groups`)tblTmp)", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "ALTER TABLE `Groups` ADD FOREIGN KEY (ParentId) REFERENCES `Groups` (Id) ON DELETE CASCADE", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + diff --git a/db/zm_update-1.35.16.sql b/db/zm_update-1.35.16.sql new file mode 100644 index 000000000..50c952bca --- /dev/null +++ b/db/zm_update-1.35.16.sql @@ -0,0 +1,12 @@ + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'DecodingEnabled' + ) > 0, + "SELECT 'Column DecodingEnabled already exists in Monitors'", + "ALTER TABLE Monitors ADD `DecodingEnabled` tinyint(3) unsigned NOT NULL default '1' AFTER `Enabled`" + )); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.17.sql b/db/zm_update-1.35.17.sql new file mode 100644 index 000000000..ec4745b8a --- /dev/null +++ b/db/zm_update-1.35.17.sql @@ -0,0 +1,14 @@ +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'Encoder' + ) > 0, +"SELECT 'Column Encoder already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `Encoder` enum('auto','h264','libx264','h264_omx','h264_vaapi','mjpeg','mpeg1','mpeg2') AFTER `OutputCodec`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; +ALTER TABLE `Monitors` MODIFY `Encoder` enum('auto','h264','libx264', 'h264_omx', 'h264_vaapi', 'mjpeg','mpeg1','mpeg2'); + +ALTER TABLE `Monitors` MODIFY `OutputCodec` INT UNSIGNED default 0; diff --git a/db/zm_update-1.35.18.sql b/db/zm_update-1.35.18.sql new file mode 100644 index 000000000..a46cb5895 --- /dev/null +++ b/db/zm_update-1.35.18.sql @@ -0,0 +1,11 @@ +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'RTSPServer' + ) > 0, +"SELECT 'Column RTSPServer already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `RTSPServer` BOOLEAN NOT NULL DEFAULT FALSE AFTER `Longitude`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.19.sql b/db/zm_update-1.35.19.sql new file mode 100644 index 000000000..8772491db --- /dev/null +++ b/db/zm_update-1.35.19.sql @@ -0,0 +1,11 @@ +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'RTSPStreamName' + ) > 0, +"SELECT 'Column RTSPStreamName already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `RTSPStreamName` varchar(255) NOT NULL default '' AFTER `RTSPServer`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.2.sql b/db/zm_update-1.35.2.sql new file mode 100644 index 000000000..55eabd887 --- /dev/null +++ b/db/zm_update-1.35.2.sql @@ -0,0 +1 @@ +ALTER TABLE Monitors MODIFY `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL','WebSite','NVSocket','VNC') NOT NULL default 'Local'; 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..16df91d74 --- /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 = 'DayEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `DayEventDiskSpace`", +"SELECT 'Column DayEventDiskSpace already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'WeekEvents' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `WeekEvents`", +"SELECT 'Column WeekEvents already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'WeekEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `WeekEventDiskSpace`", +"SELECT 'Column WeekEventDiskSpace already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'MonthEvents' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `MonthEvents`", +"SELECT 'Column MonthEvents already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'MonthEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `MonthEventDiskSpace`", +"SELECT 'Column MonthEventDiskSpace already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'ArchivedEvents' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `ArchivedEvents`", +"SELECT 'Column ArchivedEvents already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'ArchivedEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `ArchivedEventDiskSpace`", +"SELECT 'Column ArchivedEventDiskSpace already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'Importance' + ) > 0, +"SELECT 'Column Importance already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `Importance` enum('Not','Less','Normal') AFTER `RTSPStreamName`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + diff --git a/db/zm_update-1.35.26.sql b/db/zm_update-1.35.26.sql new file mode 100644 index 000000000..123f8b56a --- /dev/null +++ b/db/zm_update-1.35.26.sql @@ -0,0 +1,17 @@ +-- +-- Add Snapshot permission to Users +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Users' + AND column_name = 'Snapshots' + ) > 0, +"SELECT 'Column Snapshots already exists in Users'", +"ALTER TABLE `Users` ADD `Snapshots` enum('None','View','Edit') NOT NULL default 'None' AFTER `Devices`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +UPDATE `Users` SET `Snapshots` = `Events`; diff --git a/db/zm_update-1.35.27.sql b/db/zm_update-1.35.27.sql new file mode 100644 index 000000000..8efef50e5 --- /dev/null +++ b/db/zm_update-1.35.27.sql @@ -0,0 +1,15 @@ +-- +-- Add ModectDuringPTZ +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ModectDuringPTZ' + ) > 0, +"SELECT 'Column ModectDuringPTZ already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `ModectDuringPTZ` tinyint(3) unsigned NOT NULL default '0' AFTER `ReturnDelay`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.28.sql b/db/zm_update-1.35.28.sql new file mode 100644 index 000000000..a281507dc --- /dev/null +++ b/db/zm_update-1.35.28.sql @@ -0,0 +1 @@ +ALTER TABLE `Monitors` MODIFY `Encoder` varchar(32); diff --git a/db/zm_update-1.35.29.sql b/db/zm_update-1.35.29.sql new file mode 100644 index 000000000..341c5e162 --- /dev/null +++ b/db/zm_update-1.35.29.sql @@ -0,0 +1,47 @@ +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ManufacturerId' + ) > 0, +"SELECT 'Column ManufacturerId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `ManufacturerId` int(10) unsigned AFTER `StorageId`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ManufacturerId' + ) > 0, +"SELECT 'FOREIGN KEY for ManufacturerId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ManufacturerId`) REFERENCES `Manufacturers` (Id)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ModelId' + ) > 0, +"SELECT 'Column ModelId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `ModelId` int(10) unsigned AFTER `ManufacturerId`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ModelId' + ) > 0, +"SELECT 'FOREIGN KEY for ModelId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.3.sql b/db/zm_update-1.35.3.sql new file mode 100644 index 000000000..23f24dd3d --- /dev/null +++ b/db/zm_update-1.35.3.sql @@ -0,0 +1,2 @@ +SELECT 'ALTERING Frames.Id to a BIGINT. This could take a long time.'; +ALTER TABLE Frames MODIFY Id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT; diff --git a/db/zm_update-1.35.4.sql b/db/zm_update-1.35.4.sql new file mode 100644 index 000000000..44d195224 --- /dev/null +++ b/db/zm_update-1.35.4.sql @@ -0,0 +1,60 @@ +-- +-- This update adds ONVIF features +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'ONVIF_URL' + ) > 0, +"SELECT 'Column ONVIF_URL already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `ONVIF_URL` VARCHAR(255) NOT NULL DEFAULT '' AFTER `Triggers`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'ONVIF_Username' + ) > 0, +"SELECT 'Column ONVIF_Username already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `ONVIF_Username` VARCHAR(64) NOT NULL DEFAULT '' AFTER `ONVIF_URL`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'ONVIF_Password' + ) > 0, +"SELECT 'Column ONVIF_Password already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `ONVIF_Password` VARCHAR(64) NOT NULL DEFAULT '' AFTER `ONVIF_Username`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'ONVIF_Options' + ) > 0, +"SELECT 'Column ONVIF_Options already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `ONVIF_Options` VARCHAR(64) NOT NULL DEFAULT '' AFTER `ONVIF_Password`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + diff --git a/db/zm_update-1.35.5.sql b/db/zm_update-1.35.5.sql new file mode 100644 index 000000000..5507fcbfa --- /dev/null +++ b/db/zm_update-1.35.5.sql @@ -0,0 +1,11 @@ +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'UserId' + ) > 0, +"SELECT 'Column UserId already exists in Filters'", +"ALTER TABLE `Filters` ADD `UserId` int(10) unsigned AFTER `Name`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.6.sql b/db/zm_update-1.35.6.sql new file mode 100644 index 000000000..3512534be --- /dev/null +++ b/db/zm_update-1.35.6.sql @@ -0,0 +1,2 @@ +/* This was done in 1.31.0 but zm_create.sql.in wasn't updated to match. */ +ALTER TABLE Monitors MODIFY LinkedMonitors varchar(255); diff --git a/db/zm_update-1.35.7.sql b/db/zm_update-1.35.7.sql new file mode 100644 index 000000000..84c3dbb1e --- /dev/null +++ b/db/zm_update-1.35.7.sql @@ -0,0 +1,23 @@ +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'Latitude' + ) > 0, +"SELECT 'Column Latitude already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `Latitude` DECIMAL(10,8) AFTER `Refresh`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'Longitude' + ) > 0, +"SELECT 'Column Longitude already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `Longitude` DECIMAL(10,8) AFTER `Latitude`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.8.sql b/db/zm_update-1.35.8.sql new file mode 100644 index 000000000..1549903ab --- /dev/null +++ b/db/zm_update-1.35.8.sql @@ -0,0 +1,12 @@ +/* The MEMORY TABLE TYPE IS BAD! Switch to regular InnoDB */ + +DROP TABLE IF EXISTS `Monitor_Status`; +CREATE TABLE `Monitor_Status` ( + `MonitorId` int(10) unsigned NOT NULL, + `Status` enum('Unknown','NotRunning','Running','Connected','Signal') NOT NULL default 'Unknown', + `CaptureFPS` DECIMAL(10,2) NOT NULL default 0, + `AnalysisFPS` DECIMAL(5,2) NOT NULL default 0, + `CaptureBandwidth` INT NOT NULL default 0, + PRIMARY KEY (`MonitorId`) +) ENGINE=InnoDB; + diff --git a/db/zm_update-1.35.9.sql b/db/zm_update-1.35.9.sql new file mode 100644 index 000000000..b5f807225 --- /dev/null +++ b/db/zm_update-1.35.9.sql @@ -0,0 +1,21 @@ +-- +-- This adds Sessions Table +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.TABLES + WHERE table_name = 'Sessions' + AND table_schema = DATABASE() + ) > 0, + "SELECT 'Sessions table exists'", + "CREATE TABLE Sessions ( + id char(32) not null, + access INT(10) UNSIGNED DEFAULT NULL, + data text, + PRIMARY KEY(id) +) ENGINE=InnoDB;" + )); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/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.10.sql b/db/zm_update-1.37.10.sql new file mode 100644 index 000000000..30c2d8766 --- /dev/null +++ b/db/zm_update-1.37.10.sql @@ -0,0 +1,20 @@ +-- +-- Update Config table to have Private +-- + +SELECT 'Checking for Private in Config'; +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Config' + AND table_schema = DATABASE() + AND column_name = 'Private' + ) > 0, +"SELECT 'Column Private already exists in Config'", +"ALTER TABLE `Config` ADD COLUMN `Private` BOOLEAN NOT NULL DEFAULT FALSE AFTER `Readonly`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + + diff --git a/db/zm_update-1.37.11.sql b/db/zm_update-1.37.11.sql new file mode 100644 index 000000000..58a3d0cf1 --- /dev/null +++ b/db/zm_update-1.37.11.sql @@ -0,0 +1,18 @@ +-- +-- Update Monitors table to have use_Amcrest_API +-- + +SELECT 'Checking for use_Amcrest_API in Monitors'; +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'use_Amcrest_API' + ) > 0, +"SELECT 'Column use_Amcrest_API already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `use_Amcrest_API` BOOLEAN NOT NULL default false AFTER `ONVIF_Event_Listener`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; 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/db/zm_update-1.37.3.sql b/db/zm_update-1.37.3.sql new file mode 100644 index 000000000..5ae4aa1b1 --- /dev/null +++ b/db/zm_update-1.37.3.sql @@ -0,0 +1,73 @@ +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ManufacturerId' + ) > 0, +"SELECT 'Column ManufacturerId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `ManufacturerId` int(10) unsigned AFTER `StorageId`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ManufacturerId' + ) > 0, +"SELECT 'FOREIGN KEY for ManufacturerId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ManufacturerId`) REFERENCES `Manufacturers` (Id)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ModelId' + ) > 0, +"SELECT 'Column ModelId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `ModelId` int(10) unsigned AFTER `ManufacturerId`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ModelId' + ) > 0, +"SELECT 'FOREIGN KEY for ModelId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'MonitorPresets' + AND column_name = 'ModelId' + ) > 0, +"SELECT 'Column ModelId already exists in MonitorPresets'", +"ALTER TABLE `MonitorPresets` ADD `ModelId` int(10) unsigned AFTER `Id`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() + AND table_name = 'MonitorPresets' + AND column_name = 'ModelId' + ) > 0, +"SELECT 'FOREIGN KEY for ModelId already exists in MonitorPresets'", +"ALTER TABLE `MonitorPresets` ADD FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +UPDATE `MonitorPresets` SET `ModelId`=(SELECT `Id` FROM `Models` WHERE `Name`='IP8M-T2499EW') WHERE `Name` like 'Amcrest, IP8M-T2499EW +%'; diff --git a/db/zm_update-1.37.4.sql.in b/db/zm_update-1.37.4.sql.in new file mode 100644 index 000000000..d0ab7b36c --- /dev/null +++ b/db/zm_update-1.37.4.sql.in @@ -0,0 +1,2 @@ +source @PKGDATADIR@/db/manufacturers.sql +source @PKGDATADIR@/db/models.sql diff --git a/db/zm_update-1.37.5.sql b/db/zm_update-1.37.5.sql new file mode 100644 index 000000000..035a73a1a --- /dev/null +++ b/db/zm_update-1.37.5.sql @@ -0,0 +1,31 @@ +-- +-- This update adds EventStartCommand and EventEndCommand +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'EventEndCommand' + ) > 0, +"SELECT 'Column EventEndCommand already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `EventEndCommand` VARCHAR(255) NOT NULL DEFAULT '' AFTER `Triggers`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'EventStartCommand' + ) > 0, +"SELECT 'Column EventStartCommand already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `EventStartCommand` VARCHAR(255) NOT NULL DEFAULT '' AFTER `Triggers`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.37.6.sql b/db/zm_update-1.37.6.sql new file mode 100644 index 000000000..7c79886ad --- /dev/null +++ b/db/zm_update-1.37.6.sql @@ -0,0 +1,18 @@ +-- +-- Update Filters table to have a ExecuteInterval Column +-- + +SELECT 'Checking for ExecuteInterval in Filters'; +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Filters' + AND table_schema = DATABASE() + AND column_name = 'ExecuteInterval' + ) > 0, +"SELECT 'Column ExecuteInterval already exists in Filters'", +"ALTER TABLE Filters ADD COLUMN `ExecuteInterval` int(10) unsigned NOT NULL default '60' AFTER `UserId`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.37.7.sql b/db/zm_update-1.37.7.sql new file mode 100644 index 000000000..d7d592974 --- /dev/null +++ b/db/zm_update-1.37.7.sql @@ -0,0 +1,21 @@ +/* Change Cause from varchar(32) to TEXT. We now include alarmed zone name */ +ALTER TABLE `Events` MODIFY `Cause` TEXT; + +-- +-- Update Monitors table to have a ONVIF_Event_Listener Column +-- + +SELECT 'Checking for ONVIF_Event_Listener in Monitors'; +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'ONVIF_Event_Listener' + ) > 0, +"SELECT 'Column ONVIF_Event_Listener already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `ONVIF_Event_Listener` BOOLEAN NOT NULL default false AFTER `ONVIF_Options`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.37.8.sql b/db/zm_update-1.37.8.sql new file mode 100644 index 000000000..650489e7b --- /dev/null +++ b/db/zm_update-1.37.8.sql @@ -0,0 +1,18 @@ +-- +-- Update Monitors table to have JanusEnabled +-- + +SELECT 'Checking for JanusEnabled in Monitors'; +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'JanusEnabled' + ) > 0, +"SELECT 'Column JanusEnabled already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `JanusEnabled` BOOLEAN NOT NULL default false AFTER `DecodingEnabled`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.37.9.sql b/db/zm_update-1.37.9.sql new file mode 100644 index 000000000..be7d2a9ec --- /dev/null +++ b/db/zm_update-1.37.9.sql @@ -0,0 +1,18 @@ +-- +-- Update Monitors table to have JanusEnabled +-- + +SELECT 'Checking for JanusAudioEnabled in Monitors'; +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'JanusAudioEnabled' + ) > 0, +"SELECT 'Column JanusAudioEnabled already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `JanusAudioEnabled` BOOLEAN NOT NULL default false AFTER `JanusEnabled`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/dep/CMakeLists.txt b/dep/CMakeLists.txt new file mode 100644 index 000000000..792048adf --- /dev/null +++ b/dep/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(jwt-cpp) +add_subdirectory(libbcrypt) +add_subdirectory(RtspServer) +add_subdirectory(span-lite) diff --git a/dep/RtspServer b/dep/RtspServer new file mode 160000 index 000000000..eab328514 --- /dev/null +++ b/dep/RtspServer @@ -0,0 +1 @@ +Subproject commit eab32851421ffe54fec0229c3efc44c642bc8d46 diff --git a/dep/jwt-cpp/CMakeLists.txt b/dep/jwt-cpp/CMakeLists.txt new file mode 100644 index 000000000..81ddc84a1 --- /dev/null +++ b/dep/jwt-cpp/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(jwt-cpp INTERFACE) +add_library(jwt-cpp::jwt-cpp ALIAS jwt-cpp) + +target_include_directories(jwt-cpp + INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/src/jwt-cpp/Doxyfile b/dep/jwt-cpp/Doxyfile similarity index 99% rename from src/jwt-cpp/Doxyfile rename to dep/jwt-cpp/Doxyfile index e3303d2b9..0e912fd79 100644 --- a/src/jwt-cpp/Doxyfile +++ b/dep/jwt-cpp/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "JWT-C++" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = +PROJECT_NUMBER = 0.5.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -889,7 +889,7 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = *nlohmann*, *picojson* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -900,13 +900,13 @@ EXCLUDE_PATTERNS = # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* -EXCLUDE_SYMBOLS = +EXCLUDE_SYMBOLS = jwt::details # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = +EXAMPLE_PATH = example # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and @@ -1666,7 +1666,7 @@ EXTRA_SEARCH_MAPPINGS = # If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. # The default value is: YES. -GENERATE_LATEX = YES +GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of diff --git a/src/jwt-cpp/LICENSE b/dep/jwt-cpp/LICENSE similarity index 100% rename from src/jwt-cpp/LICENSE rename to dep/jwt-cpp/LICENSE diff --git a/dep/jwt-cpp/README.md b/dep/jwt-cpp/README.md new file mode 100644 index 000000000..5e3903262 --- /dev/null +++ b/dep/jwt-cpp/README.md @@ -0,0 +1,208 @@ +# ![logo](https://raw.githubusercontent.com/Thalhammer/jwt-cpp/master/.github/logo.svg) + +[![License Badge](https://img.shields.io/github/license/Thalhammer/jwt-cpp)](https://github.com/Thalhammer/jwt-cpp/blob/master/LICENSE) +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/5f7055e294744901991fd0a1620b231d)](https://app.codacy.com/app/Thalhammer/jwt-cpp?utm_source=github.com&utm_medium=referral&utm_content=Thalhammer/jwt-cpp&utm_campaign=Badge_Grade_Settings) +[![Linux Badge][Linux]][Cross-Platform] +[![MacOS Badge][MacOS]][Cross-Platform] +[![Windows Badge][Windows]][Cross-Platform] +[![Coverage Status](https://coveralls.io/repos/github/Thalhammer/jwt-cpp/badge.svg?branch=master)](https://coveralls.io/github/Thalhammer/jwt-cpp?branch=master) +[![Documentation Badge](https://img.shields.io/badge/Documentation-master-blue)](https://thalhammer.github.io/jwt-cpp/) +[![GitHub release (latest SemVer including pre-releases)](https://img.shields.io/github/v/release/Thalhammer/jwt-cpp?include_prereleases)](https://github.com/Thalhammer/jwt-cpp/releases) +[![Stars Badge](https://img.shields.io/github/stars/Thalhammer/jwt-cpp)](https://github.com/Thalhammer/jwt-cpp/stargazers) + +[Linux]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/cross-platform/ubuntu-latest/shields.json +[MacOS]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/cross-platform/macos-latest/shields.json +[Windows]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/cross-platform/windows-latest/shields.json +[Cross-Platform]: https://github.com/Thalhammer/jwt-cpp/actions?query=workflow%3A%22Cross-Platform+CI%22 + +A header only library for creating and validating [JSON Web Tokens](https://tools.ietf.org/html/rfc7519) in C++11. For a great introduction, [read this](https://jwt.io/introduction/). + +## Signature algorithms + +jwt-cpp supports all the algorithms defined by the specifications. The modular design allows to easily add additional algorithms without any problems. If you need any feel free to create a pull request or [open an issue](https://github.com/Thalhammer/jwt-cpp/issues/new). + +For completeness, here is a list of all supported algorithms: + +| HMSC | RSA | ECDSA | PSS | EdDSA | +| ----- | ----- | ----- | ----- | ------- | +| HS256 | RS256 | ES256 | PS256 | Ed25519 | +| HS384 | RS384 | ES384 | PS384 | Ed448 | +| HS512 | RS512 | ES512 | PS512 | | + +## SSL Compatibility + +In the name of flexibility and extensibility, jwt-cpp supports both [OpenSSL](https://github.com/openssl/openssl) and [LibreSSL](https://github.com/libressl-portable/portable). These are the version which are, or have been, tested: + +| OpenSSL | LibreSSL | +| -------------- | --------------- | +| [1.0.2][1.0.2] | ![3.1.5][3.1] | +| 1.1.0 | ![3.2.3][3.2] | +| [1.1.1][1.1.1] | ![3.3.1][3.3] | + +[1.0.2]: https://travis-ci.com/github/Thalhammer/jwt-cpp +[1.1.1]: https://github.com/Thalhammer/jwt-cpp/actions?query=workflow%3A%22Coverage+CI%22 +[3.1]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/libressl/3.1.5/shields.json +[3.2]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/libressl/3.2.3/shields.json +[3.3]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/libressl/3.3.1/shields.json + +## Overview + +There is no hard dependency on a JSON library. Instead, there's a generic `jwt::basic_claim` which is templated around type traits, which described the semantic [JSON types](https://json-schema.org/understanding-json-schema/reference/type.html) for a value, object, array, string, number, integer and boolean, as well as methods to translate between them. + +```cpp +jwt::basic_claim claim(json::object({{"json", true},{"example", 0}})); +``` + +This allows for complete freedom when picking which libraries you want to use. For more information, [see below](#providing-your-own-json-traits-your-traits). + +In order to maintain compatibility, [picojson](https://github.com/kazuho/picojson) is still used to provide a specialized `jwt::claim` along with all helpers. Defining `JWT_DISABLE_PICOJSON` will remove this optional dependency. + +As for the base64 requirements of JWTs, this libary provides `base.h` with all the required implentation; However base64 implementations are very common, with varying degrees of performance. When providing your own base64 implementation, you can define `JWT_DISABLE_BASE64` to remove the jwt-cpp implementation. + +### Getting Started + +Simple example of decoding a token and printing all [claims](https://tools.ietf.org/html/rfc7519#section-4) ([try it out](https://github.com/Thalhammer/jwt-cpp/tree/master/example/print-claims.cpp)): + +```cpp +#include +#include + +int main() { + std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + auto decoded = jwt::decode(token); + + for(auto& e : decoded.get_payload_claims()) + std::cout << e.first << " = " << e.second << std::endl; +} +``` + +In order to verify a token you first build a verifier and use it to verify a decoded token. + +```cpp +auto verifier = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{ "secret" }) + .with_issuer("auth0"); + +verifier.verify(decoded_token); +``` + +The created verifier is stateless so you can reuse it for different tokens. + +Creating a token (and signing) is equally as easy. + +```cpp +auto token = jwt::create() + .set_issuer("auth0") + .set_type("JWS") + .set_payload_claim("sample", jwt::claim(std::string("test"))) + .sign(jwt::algorithm::hs256{"secret"}); +``` + +Here is a simple example of creating a token that will expire in one hour: + +```cpp +auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now()) + .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) + .sign(jwt::algorithm::hs256{"secret"}); +``` + +> To see more examples working with RSA public and private keys, visit our [examples](https://github.com/Thalhammer/jwt-cpp/tree/master/example)! + +### Providing your own JSON Traits + +There are several key items that need to be provided to a `jwt::basic_claim` in order for it to be interoptable with you JSON library of choice. + +* type specifications +* conversion from generic "value type" to a specific type +* serialization and parsing + +If ever you are not sure, the traits are heavily checked against static asserts to make sure you provide everything that's required. + +> :warning: Not all JSON libraries are a like, you may need to extent certain types such that it can be used by jwt-cpp. See this [example](https://github.com/Thalhammer/jwt-cpp/blob/ac3de9e69bc698a464dacb256a1b50512843f092/tests/jsoncons/JsonconsTest.cpp). + +```cpp +struct my_favorite_json_library_traits { + // Type Specifications + using value_type = json; // The generic "value type" implementation, most libraries have one + using object_type = json::object_t; // The "map type" string to value + using array_type = json::array_t; // The "list type" array of values + using string_type = std::string; // The "list of chars", must be a narrow char + using number_type = double; // The "percision type" + using integer_type = int64_t; // The "integral type" + using boolean_type = bool; // The "boolean type" + + // Translation between the implementation notion of type, to the jwt::json::type equivilant + static jwt::json::type get_type(const value_type &val) { + using jwt::json::type; + + if (val.type() == json::value_t::object) + return type::object; + if (val.type() == json::value_t::array) + return type::array; + if (val.type() == json::value_t::string) + return type::string; + if (val.type() == json::value_t::number_float) + return type::number; + if (val.type() == json::value_t::number_integer) + return type::integer; + if (val.type() == json::value_t::boolean) + return type::boolean; + + throw std::logic_error("invalid type"); + } + + // Conversion from generic value to specific type + static object_type as_object(const value_type &val); + static array_type as_array(const value_type &val); + static string_type as_string(const value_type &val); + static number_type as_number(const value_type &val); + static integer_type as_int(const value_type &val); + static boolean_type as_bool(const value_type &val); + + // serilization and parsing + static bool parse(value_type &val, string_type str); + static string_type serialize(const value_type &val); // with no extra whitespace, padding or indentation +}; +``` + +## Contributing + +If you have an improvement or found a bug feel free to [open an issue](https://github.com/Thalhammer/jwt-cpp/issues/new) or add the change and create a pull request. If you file a bug please make sure to include as much information about your environment (compiler version, etc.) as possible to help reproduce the issue. If you add a new feature please make sure to also include test cases for it. + +## Dependencies + +In order to use jwt-cpp you need the following tools. + +* libcrypto (openssl or compatible) +* libssl-dev (for the header files) +* a compiler supporting at least c++11 +* basic stl support + +In order to build the test cases you also need + +* gtest +* pthread + +## Troubleshooting + +### Expired tokens + +If you are generating tokens that seem to immediately expire, you are likely not using UTC. Specifically, +if you use `get_time` to get the current time, it likely uses localtime, while this library uses UTC, +which may be why your token is immediately expiring. Please see example above on the right way to use current time. + +### Missing \_HMAC and \_EVP_sha256 symbols on Mac + +There seems to exists a problem with the included openssl library of MacOS. Make sure you link to one provided by brew. +See [here](https://github.com/Thalhammer/jwt-cpp/issues/6) for more details. + +### Building on windows fails with syntax errors + +The header ``, which is often included in windowsprojects, defines macros for MIN and MAX which screw up std::numeric_limits. +See [here](https://github.com/Thalhammer/jwt-cpp/issues/5) for more details. To fix this do one of the following things: + +* define NOMINMAX, which suppresses this behaviour +* include this library before you include windows.h +* place `#undef max` and `#undef min` before you include this library diff --git a/dep/jwt-cpp/include/jwt-cpp/base.h b/dep/jwt-cpp/include/jwt-cpp/base.h new file mode 100644 index 000000000..c447113c9 --- /dev/null +++ b/dep/jwt-cpp/include/jwt-cpp/base.h @@ -0,0 +1,208 @@ +#ifndef JWT_CPP_BASE_H +#define JWT_CPP_BASE_H + +#include +#include +#include + +#ifdef __has_cpp_attribute +#if __has_cpp_attribute(fallthrough) +#define JWT_FALLTHROUGH [[fallthrough]] +#endif +#endif + +#ifndef JWT_FALLTHROUGH +#define JWT_FALLTHROUGH +#endif + +namespace jwt { + /** + * \brief character maps when encoding and decoding + */ + namespace alphabet { + /** + * \brief valid list of characted when working with [Base64](https://tools.ietf.org/html/rfc3548) + */ + struct base64 { + static const std::array& data() { + static constexpr std::array data{ + {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}}; + return data; + } + static const std::string& fill() { + static std::string fill{"="}; + return fill; + } + }; + /** + * \brief valid list of characted when working with [Base64URL](https://tools.ietf.org/html/rfc4648) + */ + struct base64url { + static const std::array& data() { + static constexpr std::array data{ + {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}}; + return data; + } + static const std::string& fill() { + static std::string fill{"%3d"}; + return fill; + } + }; + } // namespace alphabet + + /** + * \brief Alphabet generic methods for working with encoding/decoding the base64 family + */ + class base { + public: + template + static std::string encode(const std::string& bin) { + return encode(bin, T::data(), T::fill()); + } + template + static std::string decode(const std::string& base) { + return decode(base, T::data(), T::fill()); + } + template + static std::string pad(const std::string& base) { + return pad(base, T::fill()); + } + template + static std::string trim(const std::string& base) { + return trim(base, T::fill()); + } + + private: + static std::string encode(const std::string& bin, const std::array& alphabet, + const std::string& fill) { + size_t size = bin.size(); + std::string res; + + // clear incomplete bytes + size_t fast_size = size - size % 3; + for (size_t i = 0; i < fast_size;) { + uint32_t octet_a = static_cast(bin[i++]); + uint32_t octet_b = static_cast(bin[i++]); + uint32_t octet_c = static_cast(bin[i++]); + + uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + + res += alphabet[(triple >> 3 * 6) & 0x3F]; + res += alphabet[(triple >> 2 * 6) & 0x3F]; + res += alphabet[(triple >> 1 * 6) & 0x3F]; + res += alphabet[(triple >> 0 * 6) & 0x3F]; + } + + if (fast_size == size) return res; + + size_t mod = size % 3; + + uint32_t octet_a = fast_size < size ? static_cast(bin[fast_size++]) : 0; + uint32_t octet_b = fast_size < size ? static_cast(bin[fast_size++]) : 0; + uint32_t octet_c = fast_size < size ? static_cast(bin[fast_size++]) : 0; + + uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + + switch (mod) { + case 1: + res += alphabet[(triple >> 3 * 6) & 0x3F]; + res += alphabet[(triple >> 2 * 6) & 0x3F]; + res += fill; + res += fill; + break; + case 2: + res += alphabet[(triple >> 3 * 6) & 0x3F]; + res += alphabet[(triple >> 2 * 6) & 0x3F]; + res += alphabet[(triple >> 1 * 6) & 0x3F]; + res += fill; + break; + default: break; + } + + return res; + } + + static std::string decode(const std::string& base, const std::array& alphabet, + const std::string& fill) { + size_t size = base.size(); + + size_t fill_cnt = 0; + while (size > fill.size()) { + if (base.substr(size - fill.size(), fill.size()) == fill) { + fill_cnt++; + size -= fill.size(); + if (fill_cnt > 2) throw std::runtime_error("Invalid input"); + } else + break; + } + + if ((size + fill_cnt) % 4 != 0) throw std::runtime_error("Invalid input"); + + size_t out_size = size / 4 * 3; + std::string res; + res.reserve(out_size); + + auto get_sextet = [&](size_t offset) { + for (size_t i = 0; i < alphabet.size(); i++) { + if (alphabet[i] == base[offset]) return static_cast(i); + } + throw std::runtime_error("Invalid input"); + }; + + size_t fast_size = size - size % 4; + for (size_t i = 0; i < fast_size;) { + uint32_t sextet_a = get_sextet(i++); + uint32_t sextet_b = get_sextet(i++); + uint32_t sextet_c = get_sextet(i++); + uint32_t sextet_d = get_sextet(i++); + + uint32_t triple = (sextet_a << 3 * 6) + (sextet_b << 2 * 6) + (sextet_c << 1 * 6) + (sextet_d << 0 * 6); + + res += static_cast((triple >> 2 * 8) & 0xFFU); + res += static_cast((triple >> 1 * 8) & 0xFFU); + res += static_cast((triple >> 0 * 8) & 0xFFU); + } + + if (fill_cnt == 0) return res; + + uint32_t triple = (get_sextet(fast_size) << 3 * 6) + (get_sextet(fast_size + 1) << 2 * 6); + + switch (fill_cnt) { + case 1: + triple |= (get_sextet(fast_size + 2) << 1 * 6); + res += static_cast((triple >> 2 * 8) & 0xFFU); + res += static_cast((triple >> 1 * 8) & 0xFFU); + break; + case 2: res += static_cast((triple >> 2 * 8) & 0xFFU); break; + default: break; + } + + return res; + } + + static std::string pad(const std::string& base, const std::string& fill) { + std::string padding; + switch (base.size() % 4) { + case 1: padding += fill; JWT_FALLTHROUGH; + case 2: padding += fill; JWT_FALLTHROUGH; + case 3: padding += fill; JWT_FALLTHROUGH; + default: break; + } + + return base + padding; + } + + static std::string trim(const std::string& base, const std::string& fill) { + auto pos = base.find(fill); + return base.substr(0, pos); + } + }; +} // namespace jwt + +#endif diff --git a/dep/jwt-cpp/include/jwt-cpp/jwt.h b/dep/jwt-cpp/include/jwt-cpp/jwt.h new file mode 100644 index 000000000..bd3e6e986 --- /dev/null +++ b/dep/jwt-cpp/include/jwt-cpp/jwt.h @@ -0,0 +1,3039 @@ +#ifndef JWT_CPP_JWT_H +#define JWT_CPP_JWT_H + +#ifndef JWT_DISABLE_PICOJSON +#ifndef PICOJSON_USE_INT64 +#define PICOJSON_USE_INT64 +#endif +#include "picojson/picojson.h" +#endif + +#ifndef JWT_DISABLE_BASE64 +#include "base.h" +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if __cplusplus >= 201402L +#ifdef __has_include +#if __has_include() +#include +#endif +#endif +#endif + +// If openssl version less than 1.1 +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#define OPENSSL10 +#endif + +// If openssl version less than 1.1.1 +#if OPENSSL_VERSION_NUMBER < 0x10101000L +#define OPENSSL110 +#endif + +#if defined(LIBRESSL_VERSION_NUMBER) +#define OPENSSL10 +#define OPENSSL110 +#endif + +#ifndef JWT_CLAIM_EXPLICIT +#define JWT_CLAIM_EXPLICIT explicit +#endif + +/** + * \brief JSON Web Token + * + * A namespace to contain everything related to handling JSON Web Tokens, JWT for short, + * as a part of [RFC7519](https://tools.ietf.org/html/rfc7519), or alternatively for + * JWS (JSON Web Signature) from [RFC7515](https://tools.ietf.org/html/rfc7515) + */ +namespace jwt { + using date = std::chrono::system_clock::time_point; + + /** + * \brief Everything related to error codes issued by the library + */ + namespace error { + struct signature_verification_exception : public std::system_error { + using system_error::system_error; + }; + struct signature_generation_exception : public std::system_error { + using system_error::system_error; + }; + struct rsa_exception : public std::system_error { + using system_error::system_error; + }; + struct ecdsa_exception : public std::system_error { + using system_error::system_error; + }; + struct token_verification_exception : public std::system_error { + using system_error::system_error; + }; + /** + * \brief Errors related to processing of RSA signatures + */ + enum class rsa_error { + ok = 0, + cert_load_failed = 10, + get_key_failed, + write_key_failed, + write_cert_failed, + convert_to_pem_failed, + load_key_bio_write, + load_key_bio_read, + create_mem_bio_failed, + no_key_provided + }; + /** + * \brief Error category for RSA errors + */ + inline std::error_category& rsa_error_category() { + class rsa_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "rsa_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case rsa_error::ok: return "no error"; + case rsa_error::cert_load_failed: return "error loading cert into memory"; + case rsa_error::get_key_failed: return "error getting key from certificate"; + case rsa_error::write_key_failed: return "error writing key data in PEM format"; + case rsa_error::write_cert_failed: return "error writing cert data in PEM format"; + case rsa_error::convert_to_pem_failed: return "failed to convert key to pem"; + case rsa_error::load_key_bio_write: return "failed to load key: bio write failed"; + case rsa_error::load_key_bio_read: return "failed to load key: bio read failed"; + case rsa_error::create_mem_bio_failed: return "failed to create memory bio"; + case rsa_error::no_key_provided: return "at least one of public or private key need to be present"; + default: return "unknown RSA error"; + } + } + }; + static rsa_error_cat cat; + return cat; + } + + inline std::error_code make_error_code(rsa_error e) { return {static_cast(e), rsa_error_category()}; } + /** + * \brief Errors related to processing of RSA signatures + */ + enum class ecdsa_error { + ok = 0, + load_key_bio_write = 10, + load_key_bio_read, + create_mem_bio_failed, + no_key_provided, + invalid_key_size, + invalid_key + }; + /** + * \brief Error category for ECDSA errors + */ + inline std::error_category& ecdsa_error_category() { + class ecdsa_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "ecdsa_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case ecdsa_error::ok: return "no error"; + case ecdsa_error::load_key_bio_write: return "failed to load key: bio write failed"; + case ecdsa_error::load_key_bio_read: return "failed to load key: bio read failed"; + case ecdsa_error::create_mem_bio_failed: return "failed to create memory bio"; + case ecdsa_error::no_key_provided: + return "at least one of public or private key need to be present"; + case ecdsa_error::invalid_key_size: return "invalid key size"; + case ecdsa_error::invalid_key: return "invalid key"; + default: return "unknown ECDSA error"; + } + } + }; + static ecdsa_error_cat cat; + return cat; + } + + inline std::error_code make_error_code(ecdsa_error e) { return {static_cast(e), ecdsa_error_category()}; } + + /** + * \brief Errors related to verification of signatures + */ + enum class signature_verification_error { + ok = 0, + invalid_signature = 10, + create_context_failed, + verifyinit_failed, + verifyupdate_failed, + verifyfinal_failed, + get_key_failed + }; + /** + * \brief Error category for verification errors + */ + inline std::error_category& signature_verification_error_category() { + class verification_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "signature_verification_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case signature_verification_error::ok: return "no error"; + case signature_verification_error::invalid_signature: return "invalid signature"; + case signature_verification_error::create_context_failed: + return "failed to verify signature: could not create context"; + case signature_verification_error::verifyinit_failed: + return "failed to verify signature: VerifyInit failed"; + case signature_verification_error::verifyupdate_failed: + return "failed to verify signature: VerifyUpdate failed"; + case signature_verification_error::verifyfinal_failed: + return "failed to verify signature: VerifyFinal failed"; + case signature_verification_error::get_key_failed: + return "failed to verify signature: Could not get key"; + default: return "unknown signature verification error"; + } + } + }; + static verification_error_cat cat; + return cat; + } + + inline std::error_code make_error_code(signature_verification_error e) { + return {static_cast(e), signature_verification_error_category()}; + } + + /** + * \brief Errors related to signature generation errors + */ + enum class signature_generation_error { + ok = 0, + hmac_failed = 10, + create_context_failed, + signinit_failed, + signupdate_failed, + signfinal_failed, + ecdsa_do_sign_failed, + digestinit_failed, + digestupdate_failed, + digestfinal_failed, + rsa_padding_failed, + rsa_private_encrypt_failed, + get_key_failed + }; + /** + * \brief Error category for signature generation errors + */ + inline std::error_category& signature_generation_error_category() { + class signature_generation_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "signature_generation_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case signature_generation_error::ok: return "no error"; + case signature_generation_error::hmac_failed: return "hmac failed"; + case signature_generation_error::create_context_failed: + return "failed to create signature: could not create context"; + case signature_generation_error::signinit_failed: + return "failed to create signature: SignInit failed"; + case signature_generation_error::signupdate_failed: + return "failed to create signature: SignUpdate failed"; + case signature_generation_error::signfinal_failed: + return "failed to create signature: SignFinal failed"; + case signature_generation_error::ecdsa_do_sign_failed: return "failed to generate ecdsa signature"; + case signature_generation_error::digestinit_failed: + return "failed to create signature: DigestInit failed"; + case signature_generation_error::digestupdate_failed: + return "failed to create signature: DigestUpdate failed"; + case signature_generation_error::digestfinal_failed: + return "failed to create signature: DigestFinal failed"; + case signature_generation_error::rsa_padding_failed: + return "failed to create signature: RSA_padding_add_PKCS1_PSS_mgf1 failed"; + case signature_generation_error::rsa_private_encrypt_failed: + return "failed to create signature: RSA_private_encrypt failed"; + case signature_generation_error::get_key_failed: + return "failed to generate signature: Could not get key"; + default: return "unknown signature generation error"; + } + } + }; + static signature_generation_error_cat cat = {}; + return cat; + } + + inline std::error_code make_error_code(signature_generation_error e) { + return {static_cast(e), signature_generation_error_category()}; + } + + /** + * \brief Errors related to token verification errors + */ + enum class token_verification_error { + ok = 0, + wrong_algorithm = 10, + missing_claim, + claim_type_missmatch, + claim_value_missmatch, + token_expired, + audience_missmatch + }; + /** + * \brief Error category for token verification errors + */ + inline std::error_category& token_verification_error_category() { + class token_verification_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "token_verification_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case token_verification_error::ok: return "no error"; + case token_verification_error::wrong_algorithm: return "wrong algorithm"; + case token_verification_error::missing_claim: return "decoded JWT is missing required claim(s)"; + case token_verification_error::claim_type_missmatch: + return "claim type does not match expected type"; + case token_verification_error::claim_value_missmatch: + return "claim value does not match expected value"; + case token_verification_error::token_expired: return "token expired"; + case token_verification_error::audience_missmatch: + return "token doesn't contain the required audience"; + default: return "unknown token verification error"; + } + } + }; + static token_verification_error_cat cat = {}; + return cat; + } + + inline std::error_code make_error_code(token_verification_error e) { + return {static_cast(e), token_verification_error_category()}; + } + + inline void throw_if_error(std::error_code ec) { + if (ec) { + if (ec.category() == rsa_error_category()) throw rsa_exception(ec); + if (ec.category() == ecdsa_error_category()) throw ecdsa_exception(ec); + if (ec.category() == signature_verification_error_category()) + throw signature_verification_exception(ec); + if (ec.category() == signature_generation_error_category()) throw signature_generation_exception(ec); + if (ec.category() == token_verification_error_category()) throw token_verification_exception(ec); + } + } + } // namespace error + + // FIXME: Remove + // Keep backward compat at least for a couple of revisions + using error::ecdsa_exception; + using error::rsa_exception; + using error::signature_generation_exception; + using error::signature_verification_exception; + using error::token_verification_exception; +} // namespace jwt +namespace std { + template<> + struct is_error_code_enum : true_type {}; + template<> + struct is_error_code_enum : true_type {}; + template<> + struct is_error_code_enum : true_type {}; + template<> + struct is_error_code_enum : true_type {}; + template<> + struct is_error_code_enum : true_type {}; +} // namespace std +namespace jwt { + /** + * \brief A collection for working with certificates + * + * These _helpers_ are usefully when working with certificates OpenSSL APIs. + * For example, when dealing with JWKS (JSON Web Key Set)[https://tools.ietf.org/html/rfc7517] + * you maybe need to extract the modulus and exponent of an RSA Public Key. + */ + namespace helper { + /** + * \brief Extract the public key of a pem certificate + * + * \param certstr String containing the certificate encoded as pem + * \param pw Password used to decrypt certificate (leave empty if not encrypted) + * \param ec error_code for error_detection (gets cleared if no error occures) + */ + inline std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw, + std::error_code& ec) { + ec.clear(); +#if OPENSSL_VERSION_NUMBER <= 0x10100003L + std::unique_ptr certbio( + BIO_new_mem_buf(const_cast(certstr.data()), static_cast(certstr.size())), BIO_free_all); +#else + std::unique_ptr certbio( + BIO_new_mem_buf(certstr.data(), static_cast(certstr.size())), BIO_free_all); +#endif + std::unique_ptr keybio(BIO_new(BIO_s_mem()), BIO_free_all); + if (!certbio || !keybio) { + ec = error::rsa_error::create_mem_bio_failed; + return {}; + } + + std::unique_ptr cert( + PEM_read_bio_X509(certbio.get(), nullptr, nullptr, const_cast(pw.c_str())), X509_free); + if (!cert) { + ec = error::rsa_error::cert_load_failed; + return {}; + } + std::unique_ptr key(X509_get_pubkey(cert.get()), EVP_PKEY_free); + if (!key) { + ec = error::rsa_error::get_key_failed; + return {}; + } + if (PEM_write_bio_PUBKEY(keybio.get(), key.get()) == 0) { + ec = error::rsa_error::write_key_failed; + return {}; + } + char* ptr = nullptr; + auto len = BIO_get_mem_data(keybio.get(), &ptr); + if (len <= 0 || ptr == nullptr) { + ec = error::rsa_error::convert_to_pem_failed; + return {}; + } + return {ptr, static_cast(len)}; + } + + /** + * \brief Extract the public key of a pem certificate + * + * \param certstr String containing the certificate encoded as pem + * \param pw Password used to decrypt certificate (leave empty if not encrypted) + * \throw rsa_exception if an error occurred + */ + inline std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw = "") { + std::error_code ec; + auto res = extract_pubkey_from_cert(certstr, pw, ec); + error::throw_if_error(ec); + return res; + } + + /** + * \brief Convert the certificate provided as base64 DER to PEM. + * + * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info + * (here)[https://tools.ietf.org/html/rfc7517#section-4.7] + * + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64 decode and return + * the results. + * + * \param cert_base64_der_str String containing the certificate encoded as base64 DER + * \param decode The function to decode the cert + * \param ec error_code for error_detection (gets cleared if no error occures) + */ + template + std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, Decode decode, + std::error_code& ec) { + ec.clear(); + const auto decodedStr = decode(cert_base64_der_str); + auto c_str = reinterpret_cast(decodedStr.c_str()); + + std::unique_ptr cert(d2i_X509(NULL, &c_str, decodedStr.size()), X509_free); + std::unique_ptr certbio(BIO_new(BIO_s_mem()), BIO_free_all); + if (!cert || !certbio) { + ec = error::rsa_error::create_mem_bio_failed; + return {}; + } + + if (!PEM_write_bio_X509(certbio.get(), cert.get())) { + ec = error::rsa_error::write_cert_failed; + return {}; + } + + char* ptr = nullptr; + const auto len = BIO_get_mem_data(certbio.get(), &ptr); + if (len <= 0 || ptr == nullptr) { + ec = error::rsa_error::convert_to_pem_failed; + return {}; + } + + return {ptr, static_cast(len)}; + } + + /** + * \brief Convert the certificate provided as base64 DER to PEM. + * + * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info + * (here)[https://tools.ietf.org/html/rfc7517#section-4.7] + * + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64 decode and return + * the results. + * + * \param cert_base64_der_str String containing the certificate encoded as base64 DER + * \param decode The function to decode the cert + * \throw rsa_exception if an error occurred + */ + template + std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, Decode decode) { + std::error_code ec; + auto res = convert_base64_der_to_pem(cert_base64_der_str, std::move(decode), ec); + error::throw_if_error(ec); + return res; + } +#ifndef JWT_DISABLE_BASE64 + /** + * \brief Convert the certificate provided as base64 DER to PEM. + * + * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info + * (here)[https://tools.ietf.org/html/rfc7517#section-4.7] + * + * \param cert_base64_der_str String containing the certificate encoded as base64 DER + * \param ec error_code for error_detection (gets cleared if no error occures) + */ + inline std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, std::error_code& ec) { + auto decode = [](const std::string& token) { + return base::decode(base::pad(token)); + }; + return convert_base64_der_to_pem(cert_base64_der_str, std::move(decode), ec); + } + + /** + * \brief Convert the certificate provided as base64 DER to PEM. + * + * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info + * (here)[https://tools.ietf.org/html/rfc7517#section-4.7] + * + * \param cert_base64_der_str String containing the certificate encoded as base64 DER + * \throw rsa_exception if an error occurred + */ + inline std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str) { + std::error_code ec; + auto res = convert_base64_der_to_pem(cert_base64_der_str, ec); + error::throw_if_error(ec); + return res; + } +#endif + /** + * \brief Load a public key from a string. + * + * The string should contain a pem encoded certificate or public key + * + * \param certstr String containing the certificate encoded as pem + * \param pw Password used to decrypt certificate (leave empty if not encrypted) + * \param ec error_code for error_detection (gets cleared if no error occures) + */ + inline std::shared_ptr load_public_key_from_string(const std::string& key, + const std::string& password, std::error_code& ec) { + ec.clear(); + std::unique_ptr pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); + if (!pubkey_bio) { + ec = error::rsa_error::create_mem_bio_failed; + return nullptr; + } + if (key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { + auto epkey = helper::extract_pubkey_from_cert(key, password, ec); + if (ec) return nullptr; + const int len = static_cast(epkey.size()); + if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) { + ec = error::rsa_error::load_key_bio_write; + return nullptr; + } + } else { + const int len = static_cast(key.size()); + if (BIO_write(pubkey_bio.get(), key.data(), len) != len) { + ec = error::rsa_error::load_key_bio_write; + return nullptr; + } + } + + std::shared_ptr pkey( + PEM_read_bio_PUBKEY(pubkey_bio.get(), nullptr, nullptr, + (void*)password.data()), // NOLINT(google-readability-casting) requires `const_cast` + EVP_PKEY_free); + if (!pkey) { + ec = error::rsa_error::load_key_bio_read; + return nullptr; + } + return pkey; + } + + /** + * \brief Load a public key from a string. + * + * The string should contain a pem encoded certificate or public key + * + * \param certstr String containing the certificate or key encoded as pem + * \param pw Password used to decrypt certificate or key (leave empty if not encrypted) + * \throw rsa_exception if an error occurred + */ + inline std::shared_ptr load_public_key_from_string(const std::string& key, + const std::string& password = "") { + std::error_code ec; + auto res = load_public_key_from_string(key, password, ec); + error::throw_if_error(ec); + return res; + } + + /** + * \brief Load a private key from a string. + * + * \param key String containing a private key as pem + * \param pw Password used to decrypt key (leave empty if not encrypted) + * \param ec error_code for error_detection (gets cleared if no error occures) + */ + inline std::shared_ptr + load_private_key_from_string(const std::string& key, const std::string& password, std::error_code& ec) { + std::unique_ptr privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); + if (!privkey_bio) { + ec = error::rsa_error::create_mem_bio_failed; + return nullptr; + } + const int len = static_cast(key.size()); + if (BIO_write(privkey_bio.get(), key.data(), len) != len) { + ec = error::rsa_error::load_key_bio_write; + return nullptr; + } + std::shared_ptr pkey( + PEM_read_bio_PrivateKey(privkey_bio.get(), nullptr, nullptr, const_cast(password.c_str())), + EVP_PKEY_free); + if (!pkey) { + ec = error::rsa_error::load_key_bio_read; + return nullptr; + } + return pkey; + } + + /** + * \brief Load a private key from a string. + * + * \param key String containing a private key as pem + * \param pw Password used to decrypt key (leave empty if not encrypted) + * \throw rsa_exception if an error occurred + */ + inline std::shared_ptr load_private_key_from_string(const std::string& key, + const std::string& password = "") { + std::error_code ec; + auto res = load_private_key_from_string(key, password, ec); + error::throw_if_error(ec); + return res; + } + + /** + * Convert a OpenSSL BIGNUM to a std::string + * \param bn BIGNUM to convert + * \return bignum as string + */ + inline +#ifdef OPENSSL10 + static std::string + bn2raw(BIGNUM* bn) +#else + static std::string + bn2raw(const BIGNUM* bn) +#endif + { + std::string res(BN_num_bytes(bn), '\0'); + BN_bn2bin(bn, (unsigned char*)res.data()); // NOLINT(google-readability-casting) requires `const_cast` + return res; + } + /** + * Convert an std::string to a OpenSSL BIGNUM + * \param raw String to convert + * \return BIGNUM representation + */ + inline static std::unique_ptr raw2bn(const std::string& raw) { + return std::unique_ptr( + BN_bin2bn(reinterpret_cast(raw.data()), static_cast(raw.size()), nullptr), + BN_free); + } + } // namespace helper + + /** + * \brief Various cryptographic algorithms when working with JWT + * + * JWT (JSON Web Tokens) signatures are typically used as the payload for a JWS (JSON Web Signature) or + * JWE (JSON Web Encryption). Both of these use various cryptographic as specified by + * [RFC7518](https://tools.ietf.org/html/rfc7518) and are exposed through the a [JOSE + * Header](https://tools.ietf.org/html/rfc7515#section-4) which points to one of the JWA (JSON Web + * Algorithms)(https://tools.ietf.org/html/rfc7518#section-3.1) + */ + namespace algorithm { + /** + * \brief "none" algorithm. + * + * Returns and empty signature and checks if the given signature is empty. + */ + struct none { + /** + * \brief Return an empty string + */ + std::string sign(const std::string& /*unused*/, std::error_code& ec) const { + ec.clear(); + return {}; + } + /** + * \brief Check if the given signature is empty. + * + * JWT's with "none" algorithm should not contain a signature. + * \param signature Signature data to verify + * \param ec error_code filled with details about the error + */ + void verify(const std::string& /*unused*/, const std::string& signature, std::error_code& ec) const { + ec.clear(); + if (!signature.empty()) { ec = error::signature_verification_error::invalid_signature; } + } + /// Get algorithm name + std::string name() const { return "none"; } + }; + /** + * \brief Base class for HMAC family of algorithms + */ + struct hmacsha { + /** + * Construct new hmac algorithm + * \param key Key to use for HMAC + * \param md Pointer to hash function + * \param name Name of the algorithm + */ + hmacsha(std::string key, const EVP_MD* (*md)(), std::string name) + : secret(std::move(key)), md(md), alg_name(std::move(name)) {} + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return HMAC signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + std::string res(static_cast(EVP_MAX_MD_SIZE), '\0'); + auto len = static_cast(res.size()); + if (HMAC(md(), secret.data(), static_cast(secret.size()), + reinterpret_cast(data.data()), static_cast(data.size()), + (unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast` + &len) == nullptr) { + ec = error::signature_generation_error::hmac_failed; + return {}; + } + res.resize(len); + return res; + } + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with details about failure. + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + auto res = sign(data, ec); + if (ec) return; + + bool matched = true; + for (size_t i = 0; i < std::min(res.size(), signature.size()); i++) + if (res[i] != signature[i]) matched = false; + if (res.size() != signature.size()) matched = false; + if (!matched) { + ec = error::signature_verification_error::invalid_signature; + return; + } + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /// HMAC secrect + const std::string secret; + /// HMAC hash generator + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; + }; + /** + * \brief Base class for RSA family of algorithms + */ + struct rsa { + /** + * Construct new rsa algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + * \param md Pointer to hash function + * \param name Name of the algorithm + */ + rsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, + const std::string& private_key_password, const EVP_MD* (*md)(), std::string name) + : md(md), alg_name(std::move(name)) { + if (!private_key.empty()) { + pkey = helper::load_private_key_from_string(private_key, private_key_password); + } else if (!public_key.empty()) { + pkey = helper::load_public_key_from_string(public_key, public_key_password); + } else + throw rsa_exception(error::rsa_error::no_key_provided); + } + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return RSA signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); +#ifdef OPENSSL10 + std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_destroy); +#else + std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); +#endif + if (!ctx) { + ec = error::signature_generation_error::create_context_failed; + return {}; + } + if (!EVP_SignInit(ctx.get(), md())) { + ec = error::signature_generation_error::signinit_failed; + return {}; + } + + std::string res(EVP_PKEY_size(pkey.get()), '\0'); + unsigned int len = 0; + + if (!EVP_SignUpdate(ctx.get(), data.data(), data.size())) { + ec = error::signature_generation_error::signupdate_failed; + return {}; + } + if (EVP_SignFinal(ctx.get(), (unsigned char*)res.data(), &len, pkey.get()) == 0) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } + + res.resize(len); + return res; + } + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with details on failure + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); +#ifdef OPENSSL10 + std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_destroy); +#else + std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); +#endif + if (!ctx) { + ec = error::signature_verification_error::create_context_failed; + return; + } + if (!EVP_VerifyInit(ctx.get(), md())) { + ec = error::signature_verification_error::verifyinit_failed; + return; + } + if (!EVP_VerifyUpdate(ctx.get(), data.data(), data.size())) { + ec = error::signature_verification_error::verifyupdate_failed; + return; + } + auto res = EVP_VerifyFinal(ctx.get(), reinterpret_cast(signature.data()), + static_cast(signature.size()), pkey.get()); + if (res != 1) { + ec = error::signature_verification_error::verifyfinal_failed; + return; + } + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /// OpenSSL structure containing converted keys + std::shared_ptr pkey; + /// Hash generator + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; + }; + /** + * \brief Base class for ECDSA family of algorithms + */ + struct ecdsa { + /** + * Construct new ecdsa algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always + * fail. \param public_key_password Password to decrypt public key pem. \param private_key_password Password + * to decrypt private key pem. \param md Pointer to hash function \param name Name of the algorithm + */ + ecdsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, + const std::string& private_key_password, const EVP_MD* (*md)(), std::string name, size_t siglen) + : md(md), alg_name(std::move(name)), signature_length(siglen) { + if (!public_key.empty()) { + std::unique_ptr pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); + if (!pubkey_bio) throw ecdsa_exception(error::ecdsa_error::create_mem_bio_failed); + if (public_key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { + auto epkey = helper::extract_pubkey_from_cert(public_key, public_key_password); + const int len = static_cast(epkey.size()); + if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) + throw ecdsa_exception(error::ecdsa_error::load_key_bio_write); + } else { + const int len = static_cast(public_key.size()); + if (BIO_write(pubkey_bio.get(), public_key.data(), len) != len) + throw ecdsa_exception(error::ecdsa_error::load_key_bio_write); + } + + pkey.reset(PEM_read_bio_EC_PUBKEY( + pubkey_bio.get(), nullptr, nullptr, + (void*)public_key_password + .c_str()), // NOLINT(google-readability-casting) requires `const_cast` + EC_KEY_free); + if (!pkey) throw ecdsa_exception(error::ecdsa_error::load_key_bio_read); + size_t keysize = EC_GROUP_get_degree(EC_KEY_get0_group(pkey.get())); + if (keysize != signature_length * 4 && (signature_length != 132 || keysize != 521)) + throw ecdsa_exception(error::ecdsa_error::invalid_key_size); + } + + if (!private_key.empty()) { + std::unique_ptr privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); + if (!privkey_bio) throw ecdsa_exception(error::ecdsa_error::create_mem_bio_failed); + const int len = static_cast(private_key.size()); + if (BIO_write(privkey_bio.get(), private_key.data(), len) != len) + throw ecdsa_exception(error::ecdsa_error::load_key_bio_write); + pkey.reset(PEM_read_bio_ECPrivateKey(privkey_bio.get(), nullptr, nullptr, + const_cast(private_key_password.c_str())), + EC_KEY_free); + if (!pkey) throw ecdsa_exception(error::ecdsa_error::load_key_bio_read); + size_t keysize = EC_GROUP_get_degree(EC_KEY_get0_group(pkey.get())); + if (keysize != signature_length * 4 && (signature_length != 132 || keysize != 521)) + throw ecdsa_exception(error::ecdsa_error::invalid_key_size); + } + if (!pkey) throw ecdsa_exception(error::ecdsa_error::no_key_provided); + + if (EC_KEY_check_key(pkey.get()) == 0) throw ecdsa_exception(error::ecdsa_error::invalid_key); + } + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return ECDSA signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + const std::string hash = generate_hash(data, ec); + if (ec) return {}; + + std::unique_ptr sig( + ECDSA_do_sign(reinterpret_cast(hash.data()), static_cast(hash.size()), + pkey.get()), + ECDSA_SIG_free); + if (!sig) { + ec = error::signature_generation_error::ecdsa_do_sign_failed; + return {}; + } +#ifdef OPENSSL10 + + auto rr = helper::bn2raw(sig->r); + auto rs = helper::bn2raw(sig->s); +#else + const BIGNUM* r; + const BIGNUM* s; + ECDSA_SIG_get0(sig.get(), &r, &s); + auto rr = helper::bn2raw(r); + auto rs = helper::bn2raw(s); +#endif + if (rr.size() > signature_length / 2 || rs.size() > signature_length / 2) + throw std::logic_error("bignum size exceeded expected length"); + rr.insert(0, signature_length / 2 - rr.size(), '\0'); + rs.insert(0, signature_length / 2 - rs.size(), '\0'); + return rr + rs; + } + + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with details on error + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + const std::string hash = generate_hash(data, ec); + if (ec) return; + auto r = helper::raw2bn(signature.substr(0, signature.size() / 2)); + auto s = helper::raw2bn(signature.substr(signature.size() / 2)); + +#ifdef OPENSSL10 + ECDSA_SIG sig; + sig.r = r.get(); + sig.s = s.get(); + + if (ECDSA_do_verify((const unsigned char*)hash.data(), static_cast(hash.size()), &sig, + pkey.get()) != 1) { + ec = error::signature_verification_error::invalid_signature; + return; + } +#else + std::unique_ptr sig(ECDSA_SIG_new(), ECDSA_SIG_free); + if (!sig) { + ec = error::signature_verification_error::create_context_failed; + return; + } + + ECDSA_SIG_set0(sig.get(), r.release(), s.release()); + + if (ECDSA_do_verify(reinterpret_cast(hash.data()), static_cast(hash.size()), + sig.get(), pkey.get()) != 1) { + ec = error::signature_verification_error::invalid_signature; + return; + } +#endif + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /** + * Hash the provided data using the hash function specified in constructor + * \param data Data to hash + * \return Hash of data + */ + std::string generate_hash(const std::string& data, std::error_code& ec) const { +#ifdef OPENSSL10 + std::unique_ptr ctx(EVP_MD_CTX_create(), + &EVP_MD_CTX_destroy); +#else + std::unique_ptr ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free); +#endif + if (!ctx) { + ec = error::signature_generation_error::create_context_failed; + return {}; + } + if (EVP_DigestInit(ctx.get(), md()) == 0) { + ec = error::signature_generation_error::digestinit_failed; + return {}; + } + if (EVP_DigestUpdate(ctx.get(), data.data(), data.size()) == 0) { + ec = error::signature_generation_error::digestupdate_failed; + return {}; + } + unsigned int len = 0; + std::string res(EVP_MD_CTX_size(ctx.get()), '\0'); + if (EVP_DigestFinal( + ctx.get(), + (unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast` + &len) == 0) { + ec = error::signature_generation_error::digestfinal_failed; + return {}; + } + res.resize(len); + return res; + } + + /// OpenSSL struct containing keys + std::shared_ptr pkey; + /// Hash generator function + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; + /// Length of the resulting signature + const size_t signature_length; + }; + +#ifndef OPENSSL110 + /** + * \brief Base class for EdDSA family of algorithms + * + * https://tools.ietf.org/html/rfc8032 + * + * The EdDSA algorithms were introduced in [OpenSSL v1.1.1](https://www.openssl.org/news/openssl-1.1.1-notes.html), + * so these algorithms are only available when building against this version or higher. + */ + struct eddsa { + /** + * Construct new eddsa algorithm + * \param public_key EdDSA public key in PEM format + * \param private_key EdDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + * \param name Name of the algorithm + */ + eddsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, + const std::string& private_key_password, std::string name) + : alg_name(std::move(name)) { + if (!private_key.empty()) { + pkey = helper::load_private_key_from_string(private_key, private_key_password); + } else if (!public_key.empty()) { + pkey = helper::load_public_key_from_string(public_key, public_key_password); + } else + throw ecdsa_exception(error::ecdsa_error::load_key_bio_read); + } + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return EdDSA signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); + if (!ctx) { + ec = error::signature_generation_error::create_context_failed; + return {}; + } + if (!EVP_DigestSignInit(ctx.get(), nullptr, nullptr, nullptr, pkey.get())) { + ec = error::signature_generation_error::signinit_failed; + return {}; + } + + size_t len = EVP_PKEY_size(pkey.get()); + std::string res(len, '\0'); + +// LibreSSL is the special kid in the block, as it does not support EVP_DigestSign. +// OpenSSL on the otherhand does not support using EVP_DigestSignUpdate for eddsa, which is why we end up with this +// mess. +#ifdef LIBRESSL_VERSION_NUMBER + ERR_clear_error(); + if (EVP_DigestSignUpdate(ctx.get(), reinterpret_cast(data.data()), data.size()) != + 1) { + std::cout << ERR_error_string(ERR_get_error(), NULL) << std::endl; + ec = error::signature_generation_error::signupdate_failed; + return {}; + } + if (EVP_DigestSignFinal(ctx.get(), reinterpret_cast(&res[0]), &len) != 1) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } +#else + if (EVP_DigestSign(ctx.get(), reinterpret_cast(&res[0]), &len, + reinterpret_cast(data.data()), data.size()) != 1) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } +#endif + + res.resize(len); + return res; + } + + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with details on error + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); + if (!ctx) { + ec = error::signature_verification_error::create_context_failed; + return; + } + if (!EVP_DigestVerifyInit(ctx.get(), nullptr, nullptr, nullptr, pkey.get())) { + ec = error::signature_verification_error::verifyinit_failed; + return; + } +// LibreSSL is the special kid in the block, as it does not support EVP_DigestVerify. +// OpenSSL on the otherhand does not support using EVP_DigestVerifyUpdate for eddsa, which is why we end up with this +// mess. +#ifdef LIBRESSL_VERSION_NUMBER + if (EVP_DigestVerifyUpdate(ctx.get(), reinterpret_cast(data.data()), + data.size()) != 1) { + ec = error::signature_verification_error::verifyupdate_failed; + return; + } + if (EVP_DigestVerifyFinal(ctx.get(), reinterpret_cast(signature.data()), + signature.size()) != 1) { + ec = error::signature_verification_error::verifyfinal_failed; + return; + } +#else + auto res = EVP_DigestVerify(ctx.get(), reinterpret_cast(signature.data()), + signature.size(), reinterpret_cast(data.data()), + data.size()); + if (res != 1) { + ec = error::signature_verification_error::verifyfinal_failed; + return; + } +#endif + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /// OpenSSL struct containing keys + std::shared_ptr pkey; + /// algorithm's name + const std::string alg_name; + }; +#endif + /** + * \brief Base class for PSS-RSA family of algorithms + */ + struct pss { + /** + * Construct new pss algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + * \param md Pointer to hash function + * \param name Name of the algorithm + */ + pss(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, + const std::string& private_key_password, const EVP_MD* (*md)(), std::string name) + : md(md), alg_name(std::move(name)) { + if (!private_key.empty()) { + pkey = helper::load_private_key_from_string(private_key, private_key_password); + } else if (!public_key.empty()) { + pkey = helper::load_public_key_from_string(public_key, public_key_password); + } else + throw rsa_exception(error::rsa_error::no_key_provided); + } + + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return ECDSA signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + auto hash = this->generate_hash(data, ec); + if (ec) return {}; + + std::unique_ptr key(EVP_PKEY_get1_RSA(pkey.get()), RSA_free); + if (!key) { + ec = error::signature_generation_error::get_key_failed; + return {}; + } + const int size = RSA_size(key.get()); + + std::string padded(size, 0x00); + if (RSA_padding_add_PKCS1_PSS_mgf1( + key.get(), (unsigned char*)padded.data(), reinterpret_cast(hash.data()), + md(), md(), -1) == 0) { // NOLINT(google-readability-casting) requires `const_cast` + ec = error::signature_generation_error::rsa_padding_failed; + return {}; + } + + std::string res(size, 0x00); + if (RSA_private_encrypt(size, reinterpret_cast(padded.data()), + (unsigned char*)res.data(), key.get(), RSA_NO_PADDING) < + 0) { // NOLINT(google-readability-casting) requires `const_cast` + ec = error::signature_generation_error::rsa_private_encrypt_failed; + return {}; + } + return res; + } + + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with error details + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + auto hash = this->generate_hash(data, ec); + if (ec) return; + + std::unique_ptr key(EVP_PKEY_get1_RSA(pkey.get()), RSA_free); + if (!key) { + ec = error::signature_verification_error::get_key_failed; + return; + } + const int size = RSA_size(key.get()); + + std::string sig(size, 0x00); + if (RSA_public_decrypt( + static_cast(signature.size()), reinterpret_cast(signature.data()), + (unsigned char*)sig.data(), // NOLINT(google-readability-casting) requires `const_cast` + key.get(), RSA_NO_PADDING) == 0) { + ec = error::signature_verification_error::invalid_signature; + return; + } + + if (RSA_verify_PKCS1_PSS_mgf1(key.get(), reinterpret_cast(hash.data()), md(), + md(), reinterpret_cast(sig.data()), -1) == 0) { + ec = error::signature_verification_error::invalid_signature; + return; + } + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /** + * Hash the provided data using the hash function specified in constructor + * \param data Data to hash + * \return Hash of data + */ + std::string generate_hash(const std::string& data, std::error_code& ec) const { +#ifdef OPENSSL10 + std::unique_ptr ctx(EVP_MD_CTX_create(), + &EVP_MD_CTX_destroy); +#else + std::unique_ptr ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free); +#endif + if (!ctx) { + ec = error::signature_generation_error::create_context_failed; + return {}; + } + if (EVP_DigestInit(ctx.get(), md()) == 0) { + ec = error::signature_generation_error::digestinit_failed; + return {}; + } + if (EVP_DigestUpdate(ctx.get(), data.data(), data.size()) == 0) { + ec = error::signature_generation_error::digestupdate_failed; + return {}; + } + unsigned int len = 0; + std::string res(EVP_MD_CTX_size(ctx.get()), '\0'); + if (EVP_DigestFinal(ctx.get(), (unsigned char*)res.data(), &len) == + 0) { // NOLINT(google-readability-casting) requires `const_cast` + ec = error::signature_generation_error::digestfinal_failed; + return {}; + } + res.resize(len); + return res; + } + + /// OpenSSL structure containing keys + std::shared_ptr pkey; + /// Hash generator function + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; + }; + + /** + * HS256 algorithm + */ + struct hs256 : public hmacsha { + /** + * Construct new instance of algorithm + * \param key HMAC signing key + */ + explicit hs256(std::string key) : hmacsha(std::move(key), EVP_sha256, "HS256") {} + }; + /** + * HS384 algorithm + */ + struct hs384 : public hmacsha { + /** + * Construct new instance of algorithm + * \param key HMAC signing key + */ + explicit hs384(std::string key) : hmacsha(std::move(key), EVP_sha384, "HS384") {} + }; + /** + * HS512 algorithm + */ + struct hs512 : public hmacsha { + /** + * Construct new instance of algorithm + * \param key HMAC signing key + */ + explicit hs512(std::string key) : hmacsha(std::move(key), EVP_sha512, "HS512") {} + }; + /** + * RS256 algorithm + */ + struct rs256 : public rsa { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit rs256(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "RS256") {} + }; + /** + * RS384 algorithm + */ + struct rs384 : public rsa { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit rs384(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "RS384") {} + }; + /** + * RS512 algorithm + */ + struct rs512 : public rsa { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit rs512(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "RS512") {} + }; + /** + * ES256 algorithm + */ + struct es256 : public ecdsa { + /** + * Construct new instance of algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit es256(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "ES256", 64) {} + }; + /** + * ES384 algorithm + */ + struct es384 : public ecdsa { + /** + * Construct new instance of algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit es384(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "ES384", 96) {} + }; + /** + * ES512 algorithm + */ + struct es512 : public ecdsa { + /** + * Construct new instance of algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit es512(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "ES512", 132) {} + }; + +#ifndef OPENSSL110 + /** + * Ed25519 algorithm + * + * https://en.wikipedia.org/wiki/EdDSA#Ed25519 + * + * Requires at least OpenSSL 1.1.1. + */ + struct ed25519 : public eddsa { + /** + * Construct new instance of algorithm + * \param public_key Ed25519 public key in PEM format + * \param private_key Ed25519 private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit ed25519(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : eddsa(public_key, private_key, public_key_password, private_key_password, "EdDSA") {} + }; + + /** + * Ed448 algorithm + * + * https://en.wikipedia.org/wiki/EdDSA#Ed448 + * + * Requires at least OpenSSL 1.1.1. + */ + struct ed448 : public eddsa { + /** + * Construct new instance of algorithm + * \param public_key Ed448 public key in PEM format + * \param private_key Ed448 private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit ed448(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : eddsa(public_key, private_key, public_key_password, private_key_password, "EdDSA") {} + }; +#endif + + /** + * PS256 algorithm + */ + struct ps256 : public pss { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit ps256(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "PS256") {} + }; + /** + * PS384 algorithm + */ + struct ps384 : public pss { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit ps384(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "PS384") {} + }; + /** + * PS512 algorithm + */ + struct ps512 : public pss { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit ps512(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "PS512") {} + }; + } // namespace algorithm + + /** + * \brief JSON Abstractions for working with any library + */ + namespace json { + /** + * \brief Generic JSON types used in JWTs + * + * This enum is to abstract the third party underlying types + */ + enum class type { boolean, integer, number, string, array, object }; + } // namespace json + + namespace details { +#ifdef __cpp_lib_void_t + template + using void_t = std::void_t; +#else + // https://en.cppreference.com/w/cpp/types/void_t + template + struct make_void { + using type = void; + }; + + template + using void_t = typename make_void::type; +#endif + +#ifdef __cpp_lib_experimental_detect + template class _Op, typename... _Args> + using is_detected = std::experimental::is_detected<_Op, _Args...>; + + template class _Op, typename... _Args> + using is_detected_t = std::experimental::detected_t<_Op, _Args...>; +#else + struct nonesuch { + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; + }; + + // https://en.cppreference.com/w/cpp/experimental/is_detected + template class Op, class... Args> + struct detector { + using value = std::false_type; + using type = Default; + }; + + template class Op, class... Args> + struct detector>, Op, Args...> { + using value = std::true_type; + using type = Op; + }; + + template class Op, class... Args> + using is_detected = typename detector::value; + + template class Op, class... Args> + using is_detected_t = typename detector::type; +#endif + + template + using get_type_function = decltype(traits_type::get_type); + + template + using is_get_type_signature = + typename std::is_same, json::type(const value_type&)>; + + template + struct supports_get_type { + static constexpr auto value = is_detected::value && + std::is_function>::value && + is_get_type_signature::value; + }; + + template + using as_object_function = decltype(traits_type::as_object); + + template + using is_as_object_signature = + typename std::is_same, object_type(const value_type&)>; + + template + struct supports_as_object { + static constexpr auto value = std::is_constructible::value && + is_detected::value && + std::is_function>::value && + is_as_object_signature::value; + }; + + template + using as_array_function = decltype(traits_type::as_array); + + template + using is_as_array_signature = + typename std::is_same, array_type(const value_type&)>; + + template + struct supports_as_array { + static constexpr auto value = std::is_constructible::value && + is_detected::value && + std::is_function>::value && + is_as_array_signature::value; + }; + + template + using as_string_function = decltype(traits_type::as_string); + + template + using is_as_string_signature = + typename std::is_same, string_type(const value_type&)>; + + template + struct supports_as_string { + static constexpr auto value = std::is_constructible::value && + is_detected::value && + std::is_function>::value && + is_as_string_signature::value; + }; + + template + using as_number_function = decltype(traits_type::as_number); + + template + using is_as_number_signature = + typename std::is_same, number_type(const value_type&)>; + + template + struct supports_as_number { + static constexpr auto value = std::is_floating_point::value && + std::is_constructible::value && + is_detected::value && + std::is_function>::value && + is_as_number_signature::value; + }; + + template + using as_integer_function = decltype(traits_type::as_int); + + template + using is_as_integer_signature = + typename std::is_same, integer_type(const value_type&)>; + + template + struct supports_as_integer { + static constexpr auto value = std::is_signed::value && + !std::is_floating_point::value && + std::is_constructible::value && + is_detected::value && + std::is_function>::value && + is_as_integer_signature::value; + }; + + template + using as_boolean_function = decltype(traits_type::as_bool); + + template + using is_as_boolean_signature = + typename std::is_same, boolean_type(const value_type&)>; + + template + struct supports_as_boolean { + static constexpr auto value = std::is_convertible::value && + std::is_constructible::value && + is_detected::value && + std::is_function>::value && + is_as_boolean_signature::value; + }; + + template + struct is_valid_traits { + // Internal assertions for better feedback + static_assert(supports_get_type::value, + "traits must provide `jwt::json::type get_type(const value_type&)`"); + static_assert(supports_as_object::value, + "traits must provide `object_type as_object(const value_type&)`"); + static_assert(supports_as_array::value, + "traits must provide `array_type as_array(const value_type&)`"); + static_assert(supports_as_string::value, + "traits must provide `string_type as_string(const value_type&)`"); + static_assert(supports_as_number::value, + "traits must provide `number_type as_number(const value_type&)`"); + static_assert( + supports_as_integer::value, + "traits must provide `integer_type as_int(const value_type&)`"); + static_assert( + supports_as_boolean::value, + "traits must provide `boolean_type as_bool(const value_type&)`"); + + static constexpr auto value = + supports_get_type::value && + supports_as_object::value && + supports_as_array::value && + supports_as_string::value && + supports_as_number::value && + supports_as_integer::value && + supports_as_boolean::value; + }; + + template + struct is_valid_json_value { + static constexpr auto value = + std::is_default_constructible::value && + std::is_constructible::value && // a more generic is_copy_constructible + std::is_move_constructible::value && std::is_assignable::value && + std::is_copy_assignable::value && std::is_move_assignable::value; + // TODO(cmcarthur): Stream operators + }; + + template + using has_mapped_type = typename traits_type::mapped_type; + + template + using has_key_type = typename traits_type::key_type; + + template + using has_value_type = typename traits_type::value_type; + + template + using has_iterator = typename object_type::iterator; + + template + using has_const_iterator = typename object_type::const_iterator; + + template + using is_begin_signature = + typename std::is_same().begin()), has_iterator>; + + template + using is_begin_const_signature = + typename std::is_same().begin()), has_const_iterator>; + + template + struct supports_begin { + static constexpr auto value = + is_detected::value && is_detected::value && + is_begin_signature::value && is_begin_const_signature::value; + }; + + template + using is_end_signature = + typename std::is_same().end()), has_iterator>; + + template + using is_end_const_signature = + typename std::is_same().end()), has_const_iterator>; + + template + struct supports_end { + static constexpr auto value = + is_detected::value && is_detected::value && + is_end_signature::value && is_end_const_signature::value; + }; + + template + using is_count_signature = typename std::is_integral().count(std::declval()))>; + + template + using is_subcription_operator_signature = + typename std::is_same()[std::declval()]), + value_type&>; + + template + using is_at_const_signature = + typename std::is_same().at(std::declval())), + const value_type&>; + + template + struct is_valid_json_object { + static constexpr auto value = + is_detected::value && + std::is_same::value && + is_detected::value && + std::is_same::value && + supports_begin::value && supports_end::value && + is_count_signature::value && + is_subcription_operator_signature::value && + is_at_const_signature::value; + + static constexpr auto supports_claims_transform = + value && is_detected::value && + std::is_same>::value; + }; + + template + struct is_valid_json_array { + static constexpr auto value = std::is_same::value; + }; + + template + struct is_valid_json_types { + // Internal assertions for better feedback + static_assert(is_valid_json_value::value, + "value type must meet basic requirements, default constructor, copyable, moveable"); + static_assert(is_valid_json_object::value, + "object_type must be a string_type to value_type container"); + static_assert(is_valid_json_array::value, + "array_type must be a container of value_type"); + + static constexpr auto value = is_valid_json_object::value && + is_valid_json_value::value && + is_valid_json_array::value; + }; + } // namespace details + + /** + * \brief a class to store a generic JSON value as claim + * + * The default template parameters use [picojson](https://github.com/kazuho/picojson) + * + * \tparam json_traits : JSON implementation traits + * + * \see [RFC 7519: JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) + */ + template + class basic_claim { + /** + * The reason behind this is to provide an expressive abstraction without + * over complexifying the API. For more information take the time to read + * https://github.com/nlohmann/json/issues/774. It maybe be expanded to + * support custom string types. + */ + static_assert(std::is_same::value, + "string_type must be a std::string."); + + static_assert( + details::is_valid_json_types::value, + "must staisfy json container requirements"); + static_assert(details::is_valid_traits::value, "traits must satisfy requirements"); + + typename json_traits::value_type val; + + public: + using set_t = std::set; + + basic_claim() = default; + basic_claim(const basic_claim&) = default; + basic_claim(basic_claim&&) = default; + basic_claim& operator=(const basic_claim&) = default; + basic_claim& operator=(basic_claim&&) = default; + ~basic_claim() = default; + + JWT_CLAIM_EXPLICIT basic_claim(typename json_traits::string_type s) : val(std::move(s)) {} + JWT_CLAIM_EXPLICIT basic_claim(const date& d) + : val(typename json_traits::integer_type(std::chrono::system_clock::to_time_t(d))) {} + JWT_CLAIM_EXPLICIT basic_claim(typename json_traits::array_type a) : val(std::move(a)) {} + JWT_CLAIM_EXPLICIT basic_claim(typename json_traits::value_type v) : val(std::move(v)) {} + JWT_CLAIM_EXPLICIT basic_claim(const set_t& s) : val(typename json_traits::array_type(s.begin(), s.end())) {} + template + basic_claim(Iterator begin, Iterator end) : val(typename json_traits::array_type(begin, end)) {} + + /** + * Get wrapped JSON value + * \return Wrapped JSON value + */ + typename json_traits::value_type to_json() const { return val; } + + /** + * Parse input stream into underlying JSON value + * \return input stream + */ + std::istream& operator>>(std::istream& is) { return is >> val; } + + /** + * Serialize claim to output stream from wrapped JSON value + * \return ouput stream + */ + std::ostream& operator<<(std::ostream& os) { return os << val; } + + /** + * Get type of contained JSON value + * \return Type + * \throw std::logic_error An internal error occured + */ + json::type get_type() const { return json_traits::get_type(val); } + + /** + * Get the contained JSON value as a string + * \return content as string + * \throw std::bad_cast Content was not a string + */ + typename json_traits::string_type as_string() const { return json_traits::as_string(val); } + + /** + * Get the contained JSON value as a date + * \return content as date + * \throw std::bad_cast Content was not a date + */ + date as_date() const { return std::chrono::system_clock::from_time_t(as_int()); } + + /** + * Get the contained JSON value as an array + * \return content as array + * \throw std::bad_cast Content was not an array + */ + typename json_traits::array_type as_array() const { return json_traits::as_array(val); } + + /** + * Get the contained JSON value as a set of strings + * \return content as set of strings + * \throw std::bad_cast Content was not an array of string + */ + set_t as_set() const { + set_t res; + for (const auto& e : json_traits::as_array(val)) { + res.insert(json_traits::as_string(e)); + } + return res; + } + + /** + * Get the contained JSON value as an integer + * \return content as int + * \throw std::bad_cast Content was not an int + */ + typename json_traits::integer_type as_int() const { return json_traits::as_int(val); } + + /** + * Get the contained JSON value as a bool + * \return content as bool + * \throw std::bad_cast Content was not a bool + */ + typename json_traits::boolean_type as_bool() const { return json_traits::as_bool(val); } + + /** + * Get the contained JSON value as a number + * \return content as double + * \throw std::bad_cast Content was not a number + */ + typename json_traits::number_type as_number() const { return json_traits::as_number(val); } + }; + + namespace error { + struct invalid_json_exception : public std::runtime_error { + invalid_json_exception() : runtime_error("invalid json") {} + }; + struct claim_not_present_exception : public std::out_of_range { + claim_not_present_exception() : out_of_range("claim not found") {} + }; + } // namespace error + + namespace details { + template + class map_of_claims { + typename json_traits::object_type claims; + + public: + using basic_claim_t = basic_claim; + using iterator = typename json_traits::object_type::iterator; + using const_iterator = typename json_traits::object_type::const_iterator; + + map_of_claims() = default; + map_of_claims(const map_of_claims&) = default; + map_of_claims(map_of_claims&&) = default; + map_of_claims& operator=(const map_of_claims&) = default; + map_of_claims& operator=(map_of_claims&&) = default; + + map_of_claims(typename json_traits::object_type json) : claims(std::move(json)) {} + + iterator begin() { return claims.begin(); } + iterator end() { return claims.end(); } + const_iterator cbegin() const { return claims.begin(); } + const_iterator cend() const { return claims.end(); } + const_iterator begin() const { return claims.begin(); } + const_iterator end() const { return claims.end(); } + + /** + * \brief Parse a JSON string into a map of claims + * + * The implication is that a "map of claims" is identic to a JSON object + * + * \param str JSON data to be parse as an object + * \return content as JSON object + */ + static typename json_traits::object_type parse_claims(const typename json_traits::string_type& str) { + typename json_traits::value_type val; + if (!json_traits::parse(val, str)) throw error::invalid_json_exception(); + + return json_traits::as_object(val); + }; + + /** + * Check if a claim is present in the map + * \return true if claim was present, false otherwise + */ + bool has_claim(const typename json_traits::string_type& name) const noexcept { + return claims.count(name) != 0; + } + + /** + * Get a claim by name + * + * \param name the name of the desired claim + * \return Requested claim + * \throw jwt::error::claim_not_present_exception if the claim was not present + */ + basic_claim_t get_claim(const typename json_traits::string_type& name) const { + if (!has_claim(name)) throw error::claim_not_present_exception(); + return basic_claim_t{claims.at(name)}; + } + + std::unordered_map get_claims() const { + static_assert( + details::is_valid_json_object::supports_claims_transform, + "currently there is a limitation on the internal implemantation of the `object_type` to have an " + "`std::pair` like `value_type`"); + + std::unordered_map res; + std::transform(claims.begin(), claims.end(), std::inserter(res, res.end()), + [](const typename json_traits::object_type::value_type& val) { + return std::make_pair(val.first, basic_claim_t{val.second}); + }); + return res; + } + }; + } // namespace details + + /** + * Base class that represents a token payload. + * Contains Convenience accessors for common claims. + */ + template + class payload { + protected: + details::map_of_claims payload_claims; + + public: + using basic_claim_t = basic_claim; + + /** + * Check if issuer is present ("iss") + * \return true if present, false otherwise + */ + bool has_issuer() const noexcept { return has_payload_claim("iss"); } + /** + * Check if subject is present ("sub") + * \return true if present, false otherwise + */ + bool has_subject() const noexcept { return has_payload_claim("sub"); } + /** + * Check if audience is present ("aud") + * \return true if present, false otherwise + */ + bool has_audience() const noexcept { return has_payload_claim("aud"); } + /** + * Check if expires is present ("exp") + * \return true if present, false otherwise + */ + bool has_expires_at() const noexcept { return has_payload_claim("exp"); } + /** + * Check if not before is present ("nbf") + * \return true if present, false otherwise + */ + bool has_not_before() const noexcept { return has_payload_claim("nbf"); } + /** + * Check if issued at is present ("iat") + * \return true if present, false otherwise + */ + bool has_issued_at() const noexcept { return has_payload_claim("iat"); } + /** + * Check if token id is present ("jti") + * \return true if present, false otherwise + */ + bool has_id() const noexcept { return has_payload_claim("jti"); } + /** + * Get issuer claim + * \return issuer as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_issuer() const { return get_payload_claim("iss").as_string(); } + /** + * Get subject claim + * \return subject as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_subject() const { return get_payload_claim("sub").as_string(); } + /** + * Get audience claim + * \return audience as a set of strings + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a set (Should not happen in a valid token) + */ + typename basic_claim_t::set_t get_audience() const { + auto aud = get_payload_claim("aud"); + if (aud.get_type() == json::type::string) return {aud.as_string()}; + + return aud.as_set(); + } + /** + * Get expires claim + * \return expires as a date in utc + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) + */ + date get_expires_at() const { return get_payload_claim("exp").as_date(); } + /** + * Get not valid before claim + * \return nbf date in utc + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) + */ + date get_not_before() const { return get_payload_claim("nbf").as_date(); } + /** + * Get issued at claim + * \return issued at as date in utc + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) + */ + date get_issued_at() const { return get_payload_claim("iat").as_date(); } + /** + * Get id claim + * \return id as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_id() const { return get_payload_claim("jti").as_string(); } + /** + * Check if a payload claim is present + * \return true if claim was present, false otherwise + */ + bool has_payload_claim(const typename json_traits::string_type& name) const noexcept { + return payload_claims.has_claim(name); + } + /** + * Get payload claim + * \return Requested claim + * \throw std::runtime_error If claim was not present + */ + basic_claim_t get_payload_claim(const typename json_traits::string_type& name) const { + return payload_claims.get_claim(name); + } + }; + + /** + * Base class that represents a token header. + * Contains Convenience accessors for common claims. + */ + template + class header { + protected: + details::map_of_claims header_claims; + + public: + using basic_claim_t = basic_claim; + /** + * Check if algortihm is present ("alg") + * \return true if present, false otherwise + */ + bool has_algorithm() const noexcept { return has_header_claim("alg"); } + /** + * Check if type is present ("typ") + * \return true if present, false otherwise + */ + bool has_type() const noexcept { return has_header_claim("typ"); } + /** + * Check if content type is present ("cty") + * \return true if present, false otherwise + */ + bool has_content_type() const noexcept { return has_header_claim("cty"); } + /** + * Check if key id is present ("kid") + * \return true if present, false otherwise + */ + bool has_key_id() const noexcept { return has_header_claim("kid"); } + /** + * Get algorithm claim + * \return algorithm as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_algorithm() const { return get_header_claim("alg").as_string(); } + /** + * Get type claim + * \return type as a string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_type() const { return get_header_claim("typ").as_string(); } + /** + * Get content type claim + * \return content type as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_content_type() const { return get_header_claim("cty").as_string(); } + /** + * Get key id claim + * \return key id as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_key_id() const { return get_header_claim("kid").as_string(); } + /** + * Check if a header claim is present + * \return true if claim was present, false otherwise + */ + bool has_header_claim(const typename json_traits::string_type& name) const noexcept { + return header_claims.has_claim(name); + } + /** + * Get header claim + * \return Requested claim + * \throw std::runtime_error If claim was not present + */ + basic_claim_t get_header_claim(const typename json_traits::string_type& name) const { + return header_claims.get_claim(name); + } + }; + + /** + * Class containing all information about a decoded token + */ + template + class decoded_jwt : public header, public payload { + protected: + /// Unmodifed token, as passed to constructor + const typename json_traits::string_type token; + /// Header part decoded from base64 + typename json_traits::string_type header; + /// Unmodified header part in base64 + typename json_traits::string_type header_base64; + /// Payload part decoded from base64 + typename json_traits::string_type payload; + /// Unmodified payload part in base64 + typename json_traits::string_type payload_base64; + /// Signature part decoded from base64 + typename json_traits::string_type signature; + /// Unmodified signature part in base64 + typename json_traits::string_type signature_base64; + + public: + using basic_claim_t = basic_claim; +#ifndef JWT_DISABLE_BASE64 + /** + * \brief Parses a given token + * + * \note Decodes using the jwt::base64url which supports an std::string + * + * \param token The token to parse + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + JWT_CLAIM_EXPLICIT decoded_jwt(const typename json_traits::string_type& token) + : decoded_jwt(token, [](const typename json_traits::string_type& token) { + return base::decode(base::pad(token)); + }) {} +#endif + /** + * \brief Parses a given token + * + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64url decode and + * return the results. + * \param token The token to parse + * \param decode The function to decode the token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt(const typename json_traits::string_type& token, Decode decode) : token(token) { + auto hdr_end = token.find('.'); + if (hdr_end == json_traits::string_type::npos) throw std::invalid_argument("invalid token supplied"); + auto payload_end = token.find('.', hdr_end + 1); + if (payload_end == json_traits::string_type::npos) throw std::invalid_argument("invalid token supplied"); + header_base64 = token.substr(0, hdr_end); + payload_base64 = token.substr(hdr_end + 1, payload_end - hdr_end - 1); + signature_base64 = token.substr(payload_end + 1); + + header = decode(header_base64); + payload = decode(payload_base64); + signature = decode(signature_base64); + + this->header_claims = details::map_of_claims::parse_claims(header); + this->payload_claims = details::map_of_claims::parse_claims(payload); + } + + /** + * Get token string, as passed to constructor + * \return token as passed to constructor + */ + const typename json_traits::string_type& get_token() const noexcept { return token; } + /** + * Get header part as json string + * \return header part after base64 decoding + */ + const typename json_traits::string_type& get_header() const noexcept { return header; } + /** + * Get payload part as json string + * \return payload part after base64 decoding + */ + const typename json_traits::string_type& get_payload() const noexcept { return payload; } + /** + * Get signature part as json string + * \return signature part after base64 decoding + */ + const typename json_traits::string_type& get_signature() const noexcept { return signature; } + /** + * Get header part as base64 string + * \return header part before base64 decoding + */ + const typename json_traits::string_type& get_header_base64() const noexcept { return header_base64; } + /** + * Get payload part as base64 string + * \return payload part before base64 decoding + */ + const typename json_traits::string_type& get_payload_base64() const noexcept { return payload_base64; } + /** + * Get signature part as base64 string + * \return signature part before base64 decoding + */ + const typename json_traits::string_type& get_signature_base64() const noexcept { return signature_base64; } + /** + * Get all payload claims + * \return map of claims + */ + std::unordered_map get_payload_claims() const { + return this->payload_claims.get_claims(); + } + /** + * Get all header claims + * \return map of claims + */ + std::unordered_map get_header_claims() const { + return this->header_claims.get_claims(); + } + }; + + /** + * Builder class to build and sign a new token + * Use jwt::create() to get an instance of this class. + */ + template + class builder { + typename json_traits::object_type header_claims; + typename json_traits::object_type payload_claims; + + public: + builder() = default; + /** + * Set a header claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_header_claim(const typename json_traits::string_type& id, typename json_traits::value_type c) { + header_claims[id] = std::move(c); + return *this; + } + + /** + * Set a header claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_header_claim(const typename json_traits::string_type& id, basic_claim c) { + header_claims[id] = c.to_json(); + return *this; + } + /** + * Set a payload claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_payload_claim(const typename json_traits::string_type& id, typename json_traits::value_type c) { + payload_claims[id] = std::move(c); + return *this; + } + /** + * Set a payload claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_payload_claim(const typename json_traits::string_type& id, basic_claim c) { + payload_claims[id] = c.to_json(); + return *this; + } + /** + * Set algorithm claim + * You normally don't need to do this, as the algorithm is automatically set if you don't change it. + * \param str Name of algorithm + * \return *this to allow for method chaining + */ + builder& set_algorithm(typename json_traits::string_type str) { + return set_header_claim("alg", typename json_traits::value_type(str)); + } + /** + * Set type claim + * \param str Type to set + * \return *this to allow for method chaining + */ + builder& set_type(typename json_traits::string_type str) { + return set_header_claim("typ", typename json_traits::value_type(str)); + } + /** + * Set content type claim + * \param str Type to set + * \return *this to allow for method chaining + */ + builder& set_content_type(typename json_traits::string_type str) { + return set_header_claim("cty", typename json_traits::value_type(str)); + } + /** + * Set key id claim + * \param str Key id to set + * \return *this to allow for method chaining + */ + builder& set_key_id(typename json_traits::string_type str) { + return set_header_claim("kid", typename json_traits::value_type(str)); + } + /** + * Set issuer claim + * \param str Issuer to set + * \return *this to allow for method chaining + */ + builder& set_issuer(typename json_traits::string_type str) { + return set_payload_claim("iss", typename json_traits::value_type(str)); + } + /** + * Set subject claim + * \param str Subject to set + * \return *this to allow for method chaining + */ + builder& set_subject(typename json_traits::string_type str) { + return set_payload_claim("sub", typename json_traits::value_type(str)); + } + /** + * Set audience claim + * \param a Audience set + * \return *this to allow for method chaining + */ + builder& set_audience(typename json_traits::array_type a) { + return set_payload_claim("aud", typename json_traits::value_type(a)); + } + /** + * Set audience claim + * \param aud Single audience + * \return *this to allow for method chaining + */ + builder& set_audience(typename json_traits::string_type aud) { + return set_payload_claim("aud", typename json_traits::value_type(aud)); + } + /** + * Set expires at claim + * \param d Expires time + * \return *this to allow for method chaining + */ + builder& set_expires_at(const date& d) { return set_payload_claim("exp", basic_claim(d)); } + /** + * Set not before claim + * \param d First valid time + * \return *this to allow for method chaining + */ + builder& set_not_before(const date& d) { return set_payload_claim("nbf", basic_claim(d)); } + /** + * Set issued at claim + * \param d Issued at time, should be current time + * \return *this to allow for method chaining + */ + builder& set_issued_at(const date& d) { return set_payload_claim("iat", basic_claim(d)); } + /** + * Set id claim + * \param str ID to set + * \return *this to allow for method chaining + */ + builder& set_id(const typename json_traits::string_type& str) { + return set_payload_claim("jti", typename json_traits::value_type(str)); + } + + /** + * Sign token and return result + * \tparam Algo Callable method which takes a string_type and return the signed input as a string_type + * \tparam Encode Callable method which takes a string_type and base64url safe encodes it, + * MUST return the result with no padding; trim the result. + * \param algo Instance of an algorithm to sign the token with + * \param encode Callable to transform the serialized json to base64 with no padding + * \return Final token as a string + * + * \note If the 'alg' header in not set in the token it will be set to `algo.name()` + */ + template + typename json_traits::string_type sign(const Algo& algo, Encode encode) const { + std::error_code ec; + auto res = sign(algo, encode, ec); + error::throw_if_error(ec); + return res; + } +#ifndef JWT_DISABLE_BASE64 + /** + * Sign token and return result + * + * using the `jwt::base` functions provided + * + * \param algo Instance of an algorithm to sign the token with + * \return Final token as a string + */ + template + typename json_traits::string_type sign(const Algo& algo) const { + std::error_code ec; + auto res = sign(algo, ec); + error::throw_if_error(ec); + return res; + } +#endif + + /** + * Sign token and return result + * \tparam Algo Callable method which takes a string_type and return the signed input as a string_type + * \tparam Encode Callable method which takes a string_type and base64url safe encodes it, + * MUST return the result with no padding; trim the result. + * \param algo Instance of an algorithm to sign the token with + * \param encode Callable to transform the serialized json to base64 with no padding + * \param ec error_code filled with details on error + * \return Final token as a string + * + * \note If the 'alg' header in not set in the token it will be set to `algo.name()` + */ + template + typename json_traits::string_type sign(const Algo& algo, Encode encode, std::error_code& ec) const { + // make a copy such that a builder can be re-used + typename json_traits::object_type obj_header = header_claims; + if (header_claims.count("alg") == 0) obj_header["alg"] = typename json_traits::value_type(algo.name()); + + const auto header = encode(json_traits::serialize(typename json_traits::value_type(obj_header))); + const auto payload = encode(json_traits::serialize(typename json_traits::value_type(payload_claims))); + const auto token = header + "." + payload; + + auto signature = algo.sign(token, ec); + if (ec) return {}; + + return token + "." + encode(signature); + } +#ifndef JWT_DISABLE_BASE64 + /** + * Sign token and return result + * + * using the `jwt::base` functions provided + * + * \param algo Instance of an algorithm to sign the token with + * \param ec error_code filled with details on error + * \return Final token as a string + */ + template + typename json_traits::string_type sign(const Algo& algo, std::error_code& ec) const { + return sign( + algo, + [](const typename json_traits::string_type& data) { + return base::trim(base::encode(data)); + }, + ec); + } +#endif + }; + + /** + * Verifier class used to check if a decoded token contains all claims required by your application and has a valid + * signature. + */ + template + class verifier { + struct algo_base { + virtual ~algo_base() = default; + virtual void verify(const std::string& data, const std::string& sig, std::error_code& ec) = 0; + }; + template + struct algo : public algo_base { + T alg; + explicit algo(T a) : alg(a) {} + void verify(const std::string& data, const std::string& sig, std::error_code& ec) override { + alg.verify(data, sig, ec); + } + }; + + using basic_claim_t = basic_claim; + /// Required claims + std::unordered_map claims; + /// Leeway time for exp, nbf and iat + size_t default_leeway = 0; + /// Instance of clock type + Clock clock; + /// Supported algorithms + std::unordered_map> algs; + + public: + /** + * Constructor for building a new verifier instance + * \param c Clock instance + */ + explicit verifier(Clock c) : clock(c) {} + + /** + * Set default leeway to use. + * \param leeway Default leeway to use if not specified otherwise + * \return *this to allow chaining + */ + verifier& leeway(size_t leeway) { + default_leeway = leeway; + return *this; + } + /** + * Set leeway for expires at. + * If not specified the default leeway will be used. + * \param leeway Set leeway to use for expires at. + * \return *this to allow chaining + */ + verifier& expires_at_leeway(size_t leeway) { + return with_claim("exp", basic_claim_t(std::chrono::system_clock::from_time_t(leeway))); + } + /** + * Set leeway for not before. + * If not specified the default leeway will be used. + * \param leeway Set leeway to use for not before. + * \return *this to allow chaining + */ + verifier& not_before_leeway(size_t leeway) { + return with_claim("nbf", basic_claim_t(std::chrono::system_clock::from_time_t(leeway))); + } + /** + * Set leeway for issued at. + * If not specified the default leeway will be used. + * \param leeway Set leeway to use for issued at. + * \return *this to allow chaining + */ + verifier& issued_at_leeway(size_t leeway) { + return with_claim("iat", basic_claim_t(std::chrono::system_clock::from_time_t(leeway))); + } + /** + * Set an issuer to check for. + * Check is casesensitive. + * \param iss Issuer to check for. + * \return *this to allow chaining + */ + verifier& with_issuer(const typename json_traits::string_type& iss) { + return with_claim("iss", basic_claim_t(iss)); + } + /** + * Set a subject to check for. + * Check is casesensitive. + * \param sub Subject to check for. + * \return *this to allow chaining + */ + verifier& with_subject(const typename json_traits::string_type& sub) { + return with_claim("sub", basic_claim_t(sub)); + } + /** + * Set an audience to check for. + * If any of the specified audiences is not present in the token the check fails. + * \param aud Audience to check for. + * \return *this to allow chaining + */ + verifier& with_audience(const typename basic_claim_t::set_t& aud) { + return with_claim("aud", basic_claim_t(aud)); + } + /** + * Set an audience to check for. + * If the specified audiences is not present in the token the check fails. + * \param aud Audience to check for. + * \return *this to allow chaining + */ + verifier& with_audience(const typename json_traits::string_type& aud) { + return with_claim("aud", basic_claim_t(aud)); + } + /** + * Set an id to check for. + * Check is casesensitive. + * \param id ID to check for. + * \return *this to allow chaining + */ + verifier& with_id(const typename json_traits::string_type& id) { return with_claim("jti", basic_claim_t(id)); } + /** + * Specify a claim to check for. + * \param name Name of the claim to check for + * \param c Claim to check for + * \return *this to allow chaining + */ + verifier& with_claim(const typename json_traits::string_type& name, basic_claim_t c) { + claims[name] = c; + return *this; + } + + /** + * Add an algorithm available for checking. + * \param alg Algorithm to allow + * \return *this to allow chaining + */ + template + verifier& allow_algorithm(Algorithm alg) { + algs[alg.name()] = std::make_shared>(alg); + return *this; + } + + /** + * Verify the given token. + * \param jwt Token to check + * \throw token_verification_exception Verification failed + */ + void verify(const decoded_jwt& jwt) const { + std::error_code ec; + verify(jwt, ec); + error::throw_if_error(ec); + } + /** + * Verify the given token. + * \param jwt Token to check + * \param ec error_code filled with details on error + */ + void verify(const decoded_jwt& jwt, std::error_code& ec) const { + ec.clear(); + const typename json_traits::string_type data = jwt.get_header_base64() + "." + jwt.get_payload_base64(); + const typename json_traits::string_type sig = jwt.get_signature(); + const std::string algo = jwt.get_algorithm(); + if (algs.count(algo) == 0) { + ec = error::token_verification_error::wrong_algorithm; + return; + } + algs.at(algo)->verify(data, sig, ec); + if (ec) return; + + auto assert_claim_eq = [](const decoded_jwt& jwt, const typename json_traits::string_type& key, + const basic_claim_t& c, std::error_code& ec) { + if (!jwt.has_payload_claim(key)) { + ec = error::token_verification_error::missing_claim; + return; + } + auto jc = jwt.get_payload_claim(key); + if (jc.get_type() != c.get_type()) { + ec = error::token_verification_error::claim_type_missmatch; + return; + } + if (c.get_type() == json::type::integer) { + if (c.as_date() != jc.as_date()) { + ec = error::token_verification_error::claim_value_missmatch; + return; + } + } else if (c.get_type() == json::type::array) { + auto s1 = c.as_set(); + auto s2 = jc.as_set(); + if (s1.size() != s2.size()) { + ec = error::token_verification_error::claim_value_missmatch; + return; + } + auto it1 = s1.cbegin(); + auto it2 = s2.cbegin(); + while (it1 != s1.cend() && it2 != s2.cend()) { + if (*it1++ != *it2++) { + ec = error::token_verification_error::claim_value_missmatch; + return; + } + } + } else if (c.get_type() == json::type::object) { + if (json_traits::serialize(c.to_json()) != json_traits::serialize(jc.to_json())) { + ec = error::token_verification_error::claim_value_missmatch; + return; + } + } else if (c.get_type() == json::type::string) { + if (c.as_string() != jc.as_string()) { + ec = error::token_verification_error::claim_value_missmatch; + return; + } + } else + throw std::logic_error("internal error, should be unreachable"); + }; + + auto time = clock.now(); + + if (jwt.has_expires_at()) { + auto leeway = claims.count("exp") == 1 + ? std::chrono::system_clock::to_time_t(claims.at("exp").as_date()) + : default_leeway; + auto exp = jwt.get_expires_at(); + if (time > exp + std::chrono::seconds(leeway)) { + ec = error::token_verification_error::token_expired; + return; + } + } + if (jwt.has_issued_at()) { + auto leeway = claims.count("iat") == 1 + ? std::chrono::system_clock::to_time_t(claims.at("iat").as_date()) + : default_leeway; + auto iat = jwt.get_issued_at(); + if (time < iat - std::chrono::seconds(leeway)) { + ec = error::token_verification_error::token_expired; + return; + } + } + if (jwt.has_not_before()) { + auto leeway = claims.count("nbf") == 1 + ? std::chrono::system_clock::to_time_t(claims.at("nbf").as_date()) + : default_leeway; + auto nbf = jwt.get_not_before(); + if (time < nbf - std::chrono::seconds(leeway)) { + ec = error::token_verification_error::token_expired; + return; + } + } + for (auto& c : claims) { + if (c.first == "exp" || c.first == "iat" || c.first == "nbf") { + // Nothing to do here, already checked + } else if (c.first == "aud") { + if (!jwt.has_audience()) { + ec = error::token_verification_error::audience_missmatch; + return; + } + auto aud = jwt.get_audience(); + typename basic_claim_t::set_t expected = {}; + if (c.second.get_type() == json::type::string) + expected = {c.second.as_string()}; + else + expected = c.second.as_set(); + for (auto& e : expected) { + if (aud.count(e) == 0) { + ec = error::token_verification_error::audience_missmatch; + return; + } + } + } else { + assert_claim_eq(jwt, c.first, c.second, ec); + if (ec) return; + } + } + } + }; + + /** + * Create a verifier using the given clock + * \param c Clock instance to use + * \return verifier instance + */ + template + verifier verify(Clock c) { + return verifier(c); + } + + /** + * Default clock class using std::chrono::system_clock as a backend. + */ + struct default_clock { + date now() const { return date::clock::now(); } + }; + + /** + * Return a builder instance to create a new token + */ + template + builder create() { + return builder(); + } + + /** + * Decode a token + * \param token Token to decode + * \param decode function that will pad and base64url decode the token + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt decode(const typename json_traits::string_type& token, Decode decode) { + return decoded_jwt(token, decode); + } + + /** + * Decode a token + * \param token Token to decode + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt decode(const typename json_traits::string_type& token) { + return decoded_jwt(token); + } + +#ifndef JWT_DISABLE_PICOJSON + struct picojson_traits { + using value_type = picojson::value; + using object_type = picojson::object; + using array_type = picojson::array; + using string_type = std::string; + using number_type = double; + using integer_type = int64_t; + using boolean_type = bool; + + static json::type get_type(const picojson::value& val) { + using json::type; + if (val.is()) return type::boolean; + if (val.is()) return type::integer; + if (val.is()) return type::number; + if (val.is()) return type::string; + if (val.is()) return type::array; + if (val.is()) return type::object; + + throw std::logic_error("invalid type"); + } + + static picojson::object as_object(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static std::string as_string(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static picojson::array as_array(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static int64_t as_int(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static bool as_bool(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static double as_number(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static bool parse(picojson::value& val, const std::string& str) { return picojson::parse(val, str).empty(); } + + static std::string serialize(const picojson::value& val) { return val.serialize(); } + }; + + /** + * Default JSON claim + * + * This type is the default specialization of the \ref basic_claim class which + * uses the standard template types. + */ + using claim = basic_claim; + + /** + * Create a verifier using the default clock + * \return verifier instance + */ + inline verifier verify() { + return verify(default_clock{}); + } + /** + * Return a picojson builder instance to create a new token + */ + inline builder create() { return builder(); } +#ifndef JWT_DISABLE_BASE64 + /** + * Decode a token + * \param token Token to decode + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + inline decoded_jwt decode(const std::string& token) { return decoded_jwt(token); } +#endif + /** + * Decode a token + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64url decode and + * return the results. + * \param token Token to decode + * \param decode The token to parse + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt decode(const std::string& token, Decode decode) { + return decoded_jwt(token, decode); + } +#endif +} // namespace jwt + +template +std::istream& operator>>(std::istream& is, jwt::basic_claim& c) { + return c.operator>>(is); +} + +template +std::ostream& operator<<(std::ostream& os, const jwt::basic_claim& c) { + return os << c.to_json(); +} + +#endif diff --git a/dep/jwt-cpp/include/nlohmann/json.hpp b/dep/jwt-cpp/include/nlohmann/json.hpp new file mode 100644 index 000000000..a70aaf8cb --- /dev/null +++ b/dep/jwt-cpp/include/nlohmann/json.hpp @@ -0,0 +1,25447 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 3.9.1 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2019 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef INCLUDE_NLOHMANN_JSON_HPP_ +#define INCLUDE_NLOHMANN_JSON_HPP_ + +#define NLOHMANN_JSON_VERSION_MAJOR 3 +#define NLOHMANN_JSON_VERSION_MINOR 9 +#define NLOHMANN_JSON_VERSION_PATCH 1 + +#include // all_of, find, for_each +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#include // istream, ostream +#include // random_access_iterator_tag +#include // unique_ptr +#include // accumulate +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap +#include // vector + +// #include + + +#include + +// #include + + +#include // transform +#include // array +#include // forward_list +#include // inserter, front_inserter, end +#include // map +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // unordered_map +#include // pair, declval +#include // valarray + +// #include + + +#include // exception +#include // runtime_error +#include // to_string + +// #include + + +#include // size_t + +namespace nlohmann +{ +namespace detail +{ +/// struct to capture the start position of the current token +struct position_t +{ + /// the total number of characters read + std::size_t chars_read_total = 0; + /// the number of characters read in the current line + std::size_t chars_read_current_line = 0; + /// the number of lines read + std::size_t lines_read = 0; + + /// conversion to size_t to preserve SAX interface + constexpr operator size_t() const + { + return chars_read_total; + } +}; + +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // pair +// #include +/* Hedley - https://nemequ.github.io/hedley + * Created by Evan Nemerson + * + * To the extent possible under law, the author(s) have dedicated all + * copyright and related and neighboring rights to this software to + * the public domain worldwide. This software is distributed without + * any warranty. + * + * For details, see . + * SPDX-License-Identifier: CC0-1.0 + */ + +#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 13) +#if defined(JSON_HEDLEY_VERSION) + #undef JSON_HEDLEY_VERSION +#endif +#define JSON_HEDLEY_VERSION 13 + +#if defined(JSON_HEDLEY_STRINGIFY_EX) + #undef JSON_HEDLEY_STRINGIFY_EX +#endif +#define JSON_HEDLEY_STRINGIFY_EX(x) #x + +#if defined(JSON_HEDLEY_STRINGIFY) + #undef JSON_HEDLEY_STRINGIFY +#endif +#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) + +#if defined(JSON_HEDLEY_CONCAT_EX) + #undef JSON_HEDLEY_CONCAT_EX +#endif +#define JSON_HEDLEY_CONCAT_EX(a,b) a##b + +#if defined(JSON_HEDLEY_CONCAT) + #undef JSON_HEDLEY_CONCAT +#endif +#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) + +#if defined(JSON_HEDLEY_CONCAT3_EX) + #undef JSON_HEDLEY_CONCAT3_EX +#endif +#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c + +#if defined(JSON_HEDLEY_CONCAT3) + #undef JSON_HEDLEY_CONCAT3 +#endif +#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) + +#if defined(JSON_HEDLEY_VERSION_ENCODE) + #undef JSON_HEDLEY_VERSION_ENCODE +#endif +#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) + #undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) + #undef JSON_HEDLEY_VERSION_DECODE_MINOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) + #undef JSON_HEDLEY_VERSION_DECODE_REVISION +#endif +#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) + +#if defined(JSON_HEDLEY_GNUC_VERSION) + #undef JSON_HEDLEY_GNUC_VERSION +#endif +#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#elif defined(__GNUC__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) +#endif + +#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) + #undef JSON_HEDLEY_GNUC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GNUC_VERSION) + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION) + #undef JSON_HEDLEY_MSVC_VERSION +#endif +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) +#elif defined(_MSC_FULL_VER) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) +#elif defined(_MSC_VER) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) + #undef JSON_HEDLEY_MSVC_VERSION_CHECK +#endif +#if !defined(_MSC_VER) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) +#else + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION) + #undef JSON_HEDLEY_INTEL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) +#elif defined(__INTEL_COMPILER) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_VERSION) + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION) + #undef JSON_HEDLEY_PGI_VERSION +#endif +#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) + #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) + #undef JSON_HEDLEY_PGI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PGI_VERSION) + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #undef JSON_HEDLEY_SUNPRO_VERSION +#endif +#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) +#elif defined(__SUNPRO_C) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) +#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) +#elif defined(__SUNPRO_CC) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) + #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#endif +#if defined(__EMSCRIPTEN__) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION) + #undef JSON_HEDLEY_ARM_VERSION +#endif +#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) +#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) + #undef JSON_HEDLEY_ARM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_ARM_VERSION) + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION) + #undef JSON_HEDLEY_IBM_VERSION +#endif +#if defined(__ibmxl__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) +#elif defined(__xlC__) && defined(__xlC_ver__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) +#elif defined(__xlC__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) + #undef JSON_HEDLEY_IBM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IBM_VERSION) + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_VERSION) + #undef JSON_HEDLEY_TI_VERSION +#endif +#if \ + defined(__TI_COMPILER_VERSION__) && \ + ( \ + defined(__TMS470__) || defined(__TI_ARM__) || \ + defined(__MSP430__) || \ + defined(__TMS320C2000__) \ + ) +#if (__TI_COMPILER_VERSION__ >= 16000000) + #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif +#endif + +#if defined(JSON_HEDLEY_TI_VERSION_CHECK) + #undef JSON_HEDLEY_TI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_VERSION) + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #undef JSON_HEDLEY_TI_CL2000_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) + #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #undef JSON_HEDLEY_TI_CL430_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) + #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #undef JSON_HEDLEY_TI_ARMCL_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) + #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) + #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #undef JSON_HEDLEY_TI_CL6X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) + #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #undef JSON_HEDLEY_TI_CL7X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) + #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #undef JSON_HEDLEY_TI_CLPRU_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) + #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION) + #undef JSON_HEDLEY_CRAY_VERSION +#endif +#if defined(_CRAYC) + #if defined(_RELEASE_PATCHLEVEL) + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) + #else + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) + #undef JSON_HEDLEY_CRAY_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_CRAY_VERSION) + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION) + #undef JSON_HEDLEY_IAR_VERSION +#endif +#if defined(__IAR_SYSTEMS_ICC__) + #if __VER__ > 1000 + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) + #else + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(VER / 100, __VER__ % 100, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) + #undef JSON_HEDLEY_IAR_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IAR_VERSION) + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION) + #undef JSON_HEDLEY_TINYC_VERSION +#endif +#if defined(__TINYC__) + #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) + #undef JSON_HEDLEY_TINYC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION) + #undef JSON_HEDLEY_DMC_VERSION +#endif +#if defined(__DMC__) + #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) + #undef JSON_HEDLEY_DMC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_DMC_VERSION) + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #undef JSON_HEDLEY_COMPCERT_VERSION +#endif +#if defined(__COMPCERT_VERSION__) + #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) + #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION) + #undef JSON_HEDLEY_PELLES_VERSION +#endif +#if defined(__POCC__) + #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) + #undef JSON_HEDLEY_PELLES_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PELLES_VERSION) + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION) + #undef JSON_HEDLEY_GCC_VERSION +#endif +#if \ + defined(JSON_HEDLEY_GNUC_VERSION) && \ + !defined(__clang__) && \ + !defined(JSON_HEDLEY_INTEL_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_ARM_VERSION) && \ + !defined(JSON_HEDLEY_TI_VERSION) && \ + !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ + !defined(__COMPCERT__) + #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#endif +#if \ + defined(__has_cpp_attribute) && \ + defined(__cplusplus) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#endif +#if !defined(__cplusplus) || !defined(__has_cpp_attribute) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#elif \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ + (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_BUILTIN) + #undef JSON_HEDLEY_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) +#else + #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) + #undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) + #undef JSON_HEDLEY_GCC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_FEATURE) + #undef JSON_HEDLEY_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) +#else + #define JSON_HEDLEY_HAS_FEATURE(feature) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) + #undef JSON_HEDLEY_GNUC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) + #undef JSON_HEDLEY_GCC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_EXTENSION) + #undef JSON_HEDLEY_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) +#else + #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) + #undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) + #undef JSON_HEDLEY_GCC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_WARNING) + #undef JSON_HEDLEY_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) +#else + #define JSON_HEDLEY_HAS_WARNING(warning) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) + #undef JSON_HEDLEY_GNUC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_WARNING) + #undef JSON_HEDLEY_GCC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") +# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# endif +#endif +#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x +#endif + +#if defined(JSON_HEDLEY_CONST_CAST) + #undef JSON_HEDLEY_CONST_CAST +#endif +#if defined(__cplusplus) +# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) +#elif \ + JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_REINTERPRET_CAST) + #undef JSON_HEDLEY_REINTERPRET_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) +#else + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_STATIC_CAST) + #undef JSON_HEDLEY_STATIC_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) +#else + #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_CPP_CAST) + #undef JSON_HEDLEY_CPP_CAST +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ + ((T) (expr)) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("diag_suppress=Pe137") \ + JSON_HEDLEY_DIAGNOSTIC_POP \ +# else +# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) +# endif +#else +# define JSON_HEDLEY_CPP_CAST(T, expr) (expr) +#endif + +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + defined(__clang__) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) + #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_PRAGMA(value) __pragma(value) +#else + #define JSON_HEDLEY_PRAGMA(value) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) + #undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#endif +#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) + #undef JSON_HEDLEY_DIAGNOSTIC_POP +#endif +#if defined(__clang__) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) + #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) +#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_PUSH + #define JSON_HEDLEY_DIAGNOSTIC_POP +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif + +#if defined(JSON_HEDLEY_DEPRECATED) + #undef JSON_HEDLEY_DEPRECATED +#endif +#if defined(JSON_HEDLEY_DEPRECATED_FOR) + #undef JSON_HEDLEY_DEPRECATED_FOR +#endif +#if JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) +#elif defined(__cplusplus) && (__cplusplus >= 201402L) + #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) +#elif \ + JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") +#else + #define JSON_HEDLEY_DEPRECATED(since) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) +#endif + +#if defined(JSON_HEDLEY_UNAVAILABLE) + #undef JSON_HEDLEY_UNAVAILABLE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) +#else + #define JSON_HEDLEY_UNAVAILABLE(available_since) +#endif + +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT +#endif +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#endif +#if (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) +#elif defined(_Check_return_) /* SAL */ + #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ +#else + #define JSON_HEDLEY_WARN_UNUSED_RESULT + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) +#endif + +#if defined(JSON_HEDLEY_SENTINEL) + #undef JSON_HEDLEY_SENTINEL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) + #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) +#else + #define JSON_HEDLEY_SENTINEL(position) +#endif + +#if defined(JSON_HEDLEY_NO_RETURN) + #undef JSON_HEDLEY_NO_RETURN +#endif +#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NO_RETURN __noreturn +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define JSON_HEDLEY_NO_RETURN _Noreturn +#elif defined(__cplusplus) && (__cplusplus >= 201103L) + #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#else + #define JSON_HEDLEY_NO_RETURN +#endif + +#if defined(JSON_HEDLEY_NO_ESCAPE) + #undef JSON_HEDLEY_NO_ESCAPE +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) + #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) +#else + #define JSON_HEDLEY_NO_ESCAPE +#endif + +#if defined(JSON_HEDLEY_UNREACHABLE) + #undef JSON_HEDLEY_UNREACHABLE +#endif +#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) + #undef JSON_HEDLEY_UNREACHABLE_RETURN +#endif +#if defined(JSON_HEDLEY_ASSUME) + #undef JSON_HEDLEY_ASSUME +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_ASSUME(expr) __assume(expr) +#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) + #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) +#elif \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #if defined(__cplusplus) + #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) + #else + #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) + #endif +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) + #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() +#elif defined(JSON_HEDLEY_ASSUME) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif +#if !defined(JSON_HEDLEY_ASSUME) + #if defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) + #else + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) + #endif +#endif +#if defined(JSON_HEDLEY_UNREACHABLE) + #if \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) + #else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() + #endif +#else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) +#endif +#if !defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif + +JSON_HEDLEY_DIAGNOSTIC_PUSH +#if JSON_HEDLEY_HAS_WARNING("-Wpedantic") + #pragma clang diagnostic ignored "-Wpedantic" +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) + #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif +#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) + #if defined(__clang__) + #pragma clang diagnostic ignored "-Wvariadic-macros" + #elif defined(JSON_HEDLEY_GCC_VERSION) + #pragma GCC diagnostic ignored "-Wvariadic-macros" + #endif +#endif +#if defined(JSON_HEDLEY_NON_NULL) + #undef JSON_HEDLEY_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) +#else + #define JSON_HEDLEY_NON_NULL(...) +#endif +JSON_HEDLEY_DIAGNOSTIC_POP + +#if defined(JSON_HEDLEY_PRINTF_FORMAT) + #undef JSON_HEDLEY_PRINTF_FORMAT +#endif +#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) +#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) +#else + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) +#endif + +#if defined(JSON_HEDLEY_CONSTEXPR) + #undef JSON_HEDLEY_CONSTEXPR +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) + #endif +#endif +#if !defined(JSON_HEDLEY_CONSTEXPR) + #define JSON_HEDLEY_CONSTEXPR +#endif + +#if defined(JSON_HEDLEY_PREDICT) + #undef JSON_HEDLEY_PREDICT +#endif +#if defined(JSON_HEDLEY_LIKELY) + #undef JSON_HEDLEY_LIKELY +#endif +#if defined(JSON_HEDLEY_UNLIKELY) + #undef JSON_HEDLEY_UNLIKELY +#endif +#if defined(JSON_HEDLEY_UNPREDICTABLE) + #undef JSON_HEDLEY_UNPREDICTABLE +#endif +#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) + #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) +#endif +#if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) +# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) +#elif \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) +# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ + (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ + })) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ + })) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#else +# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) +# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) +#endif +#if !defined(JSON_HEDLEY_UNPREDICTABLE) + #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) +#endif + +#if defined(JSON_HEDLEY_MALLOC) + #undef JSON_HEDLEY_MALLOC +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(14, 0, 0) + #define JSON_HEDLEY_MALLOC __declspec(restrict) +#else + #define JSON_HEDLEY_MALLOC +#endif + +#if defined(JSON_HEDLEY_PURE) + #undef JSON_HEDLEY_PURE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) +# define JSON_HEDLEY_PURE __attribute__((__pure__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) +# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ + ) +# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") +#else +# define JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_CONST) + #undef JSON_HEDLEY_CONST +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_CONST __attribute__((__const__)) +#elif \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_CONST _Pragma("no_side_effect") +#else + #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_RESTRICT) + #undef JSON_HEDLEY_RESTRICT +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT restrict +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + defined(__clang__) + #define JSON_HEDLEY_RESTRICT __restrict +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT _Restrict +#else + #define JSON_HEDLEY_RESTRICT +#endif + +#if defined(JSON_HEDLEY_INLINE) + #undef JSON_HEDLEY_INLINE +#endif +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + (defined(__cplusplus) && (__cplusplus >= 199711L)) + #define JSON_HEDLEY_INLINE inline +#elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) + #define JSON_HEDLEY_INLINE __inline__ +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_INLINE __inline +#else + #define JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_ALWAYS_INLINE) + #undef JSON_HEDLEY_ALWAYS_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) +# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) +# define JSON_HEDLEY_ALWAYS_INLINE __forceinline +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ + ) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") +#else +# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_NEVER_INLINE) + #undef JSON_HEDLEY_NEVER_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#else + #define JSON_HEDLEY_NEVER_INLINE +#endif + +#if defined(JSON_HEDLEY_PRIVATE) + #undef JSON_HEDLEY_PRIVATE +#endif +#if defined(JSON_HEDLEY_PUBLIC) + #undef JSON_HEDLEY_PUBLIC +#endif +#if defined(JSON_HEDLEY_IMPORT) + #undef JSON_HEDLEY_IMPORT +#endif +#if defined(_WIN32) || defined(__CYGWIN__) +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC __declspec(dllexport) +# define JSON_HEDLEY_IMPORT __declspec(dllimport) +#else +# if \ + JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + ( \ + defined(__TI_EABI__) && \ + ( \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ + ) \ + ) +# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) +# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) +# else +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC +# endif +# define JSON_HEDLEY_IMPORT extern +#endif + +#if defined(JSON_HEDLEY_NO_THROW) + #undef JSON_HEDLEY_NO_THROW +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NO_THROW __declspec(nothrow) +#else + #define JSON_HEDLEY_NO_THROW +#endif + +#if defined(JSON_HEDLEY_FALL_THROUGH) + #undef JSON_HEDLEY_FALL_THROUGH +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) + #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) +#elif defined(__fallthrough) /* SAL */ + #define JSON_HEDLEY_FALL_THROUGH __fallthrough +#else + #define JSON_HEDLEY_FALL_THROUGH +#endif + +#if defined(JSON_HEDLEY_RETURNS_NON_NULL) + #undef JSON_HEDLEY_RETURNS_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) + #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) +#elif defined(_Ret_notnull_) /* SAL */ + #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ +#else + #define JSON_HEDLEY_RETURNS_NON_NULL +#endif + +#if defined(JSON_HEDLEY_ARRAY_PARAM) + #undef JSON_HEDLEY_ARRAY_PARAM +#endif +#if \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__STDC_NO_VLA__) && \ + !defined(__cplusplus) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_ARRAY_PARAM(name) (name) +#else + #define JSON_HEDLEY_ARRAY_PARAM(name) +#endif + +#if defined(JSON_HEDLEY_IS_CONSTANT) + #undef JSON_HEDLEY_IS_CONSTANT +#endif +#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) + #undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#endif +/* JSON_HEDLEY_IS_CONSTEXPR_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #undef JSON_HEDLEY_IS_CONSTEXPR_ +#endif +#if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) + #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) +#endif +#if !defined(__cplusplus) +# if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) +#endif +# elif \ + ( \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) +#endif +# elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + defined(JSON_HEDLEY_INTEL_VERSION) || \ + defined(JSON_HEDLEY_TINYC_VERSION) || \ + defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ + defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ + defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ + defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ + defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ + defined(__clang__) +# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ + sizeof(void) != \ + sizeof(*( \ + 1 ? \ + ((void*) ((expr) * 0L) ) : \ +((struct { char v[sizeof(void) * 2]; } *) 1) \ + ) \ + ) \ + ) +# endif +#endif +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) +#else + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) (0) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) +#endif + +#if defined(JSON_HEDLEY_BEGIN_C_DECLS) + #undef JSON_HEDLEY_BEGIN_C_DECLS +#endif +#if defined(JSON_HEDLEY_END_C_DECLS) + #undef JSON_HEDLEY_END_C_DECLS +#endif +#if defined(JSON_HEDLEY_C_DECL) + #undef JSON_HEDLEY_C_DECL +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { + #define JSON_HEDLEY_END_C_DECLS } + #define JSON_HEDLEY_C_DECL extern "C" +#else + #define JSON_HEDLEY_BEGIN_C_DECLS + #define JSON_HEDLEY_END_C_DECLS + #define JSON_HEDLEY_C_DECL +#endif + +#if defined(JSON_HEDLEY_STATIC_ASSERT) + #undef JSON_HEDLEY_STATIC_ASSERT +#endif +#if \ + !defined(__cplusplus) && ( \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ + JSON_HEDLEY_HAS_FEATURE(c_static_assert) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + defined(_Static_assert) \ + ) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) +#elif \ + (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) +#else +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) +#endif + +#if defined(JSON_HEDLEY_NULL) + #undef JSON_HEDLEY_NULL +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) + #elif defined(NULL) + #define JSON_HEDLEY_NULL NULL + #else + #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) + #endif +#elif defined(NULL) + #define JSON_HEDLEY_NULL NULL +#else + #define JSON_HEDLEY_NULL ((void*) 0) +#endif + +#if defined(JSON_HEDLEY_MESSAGE) + #undef JSON_HEDLEY_MESSAGE +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_MESSAGE(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(message msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) +#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_WARNING) + #undef JSON_HEDLEY_WARNING +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_WARNING(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(clang warning msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_REQUIRE) + #undef JSON_HEDLEY_REQUIRE +#endif +#if defined(JSON_HEDLEY_REQUIRE_MSG) + #undef JSON_HEDLEY_REQUIRE_MSG +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) +# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") +# define JSON_HEDLEY_REQUIRE(expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), #expr, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), msg, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) +# endif +#else +# define JSON_HEDLEY_REQUIRE(expr) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) +#endif + +#if defined(JSON_HEDLEY_FLAGS) + #undef JSON_HEDLEY_FLAGS +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) + #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) +#endif + +#if defined(JSON_HEDLEY_FLAGS_CAST) + #undef JSON_HEDLEY_FLAGS_CAST +#endif +#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) +# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("warning(disable:188)") \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) +#endif + +#if defined(JSON_HEDLEY_EMPTY_BASES) + #undef JSON_HEDLEY_EMPTY_BASES +#endif +#if JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0) + #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) +#else + #define JSON_HEDLEY_EMPTY_BASES +#endif + +/* Remaining macros are deprecated. */ + +#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#endif +#if defined(__clang__) + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) +#else + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) + #undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#endif +#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) + +#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) + #undef JSON_HEDLEY_CLANG_HAS_FEATURE +#endif +#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) + +#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) + #undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#endif +#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) + +#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) + #undef JSON_HEDLEY_CLANG_HAS_WARNING +#endif +#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) + +#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ + + +// This file contains all internal macro definitions +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// C++ language standard detection +#if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 +#endif + +// disable float-equal warnings on GCC/clang +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdocumentation" +#endif + +// allow to disable exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #include + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +// allow to override assert +#if !defined(JSON_ASSERT) + #include // assert + #define JSON_ASSERT(x) assert(x) +#endif + +/*! +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 +*/ +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [&j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer, \ + class BinaryType> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +// Macros to simplify conversion from/to types + +#define NLOHMANN_JSON_EXPAND( x ) x +#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME +#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ + NLOHMANN_JSON_PASTE64, \ + NLOHMANN_JSON_PASTE63, \ + NLOHMANN_JSON_PASTE62, \ + NLOHMANN_JSON_PASTE61, \ + NLOHMANN_JSON_PASTE60, \ + NLOHMANN_JSON_PASTE59, \ + NLOHMANN_JSON_PASTE58, \ + NLOHMANN_JSON_PASTE57, \ + NLOHMANN_JSON_PASTE56, \ + NLOHMANN_JSON_PASTE55, \ + NLOHMANN_JSON_PASTE54, \ + NLOHMANN_JSON_PASTE53, \ + NLOHMANN_JSON_PASTE52, \ + NLOHMANN_JSON_PASTE51, \ + NLOHMANN_JSON_PASTE50, \ + NLOHMANN_JSON_PASTE49, \ + NLOHMANN_JSON_PASTE48, \ + NLOHMANN_JSON_PASTE47, \ + NLOHMANN_JSON_PASTE46, \ + NLOHMANN_JSON_PASTE45, \ + NLOHMANN_JSON_PASTE44, \ + NLOHMANN_JSON_PASTE43, \ + NLOHMANN_JSON_PASTE42, \ + NLOHMANN_JSON_PASTE41, \ + NLOHMANN_JSON_PASTE40, \ + NLOHMANN_JSON_PASTE39, \ + NLOHMANN_JSON_PASTE38, \ + NLOHMANN_JSON_PASTE37, \ + NLOHMANN_JSON_PASTE36, \ + NLOHMANN_JSON_PASTE35, \ + NLOHMANN_JSON_PASTE34, \ + NLOHMANN_JSON_PASTE33, \ + NLOHMANN_JSON_PASTE32, \ + NLOHMANN_JSON_PASTE31, \ + NLOHMANN_JSON_PASTE30, \ + NLOHMANN_JSON_PASTE29, \ + NLOHMANN_JSON_PASTE28, \ + NLOHMANN_JSON_PASTE27, \ + NLOHMANN_JSON_PASTE26, \ + NLOHMANN_JSON_PASTE25, \ + NLOHMANN_JSON_PASTE24, \ + NLOHMANN_JSON_PASTE23, \ + NLOHMANN_JSON_PASTE22, \ + NLOHMANN_JSON_PASTE21, \ + NLOHMANN_JSON_PASTE20, \ + NLOHMANN_JSON_PASTE19, \ + NLOHMANN_JSON_PASTE18, \ + NLOHMANN_JSON_PASTE17, \ + NLOHMANN_JSON_PASTE16, \ + NLOHMANN_JSON_PASTE15, \ + NLOHMANN_JSON_PASTE14, \ + NLOHMANN_JSON_PASTE13, \ + NLOHMANN_JSON_PASTE12, \ + NLOHMANN_JSON_PASTE11, \ + NLOHMANN_JSON_PASTE10, \ + NLOHMANN_JSON_PASTE9, \ + NLOHMANN_JSON_PASTE8, \ + NLOHMANN_JSON_PASTE7, \ + NLOHMANN_JSON_PASTE6, \ + NLOHMANN_JSON_PASTE5, \ + NLOHMANN_JSON_PASTE4, \ + NLOHMANN_JSON_PASTE3, \ + NLOHMANN_JSON_PASTE2, \ + NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) +#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) +#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) +#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) +#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) +#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) +#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) +#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) +#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) +#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) +#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) +#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) +#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) +#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) +#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) +#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) +#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) +#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) +#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) +#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) +#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) +#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) +#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) +#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) +#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) +#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) +#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) +#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) +#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) +#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) +#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) +#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) +#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) +#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) +#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) +#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) +#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) +#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) +#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) +#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) +#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) +#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) +#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) +#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) +#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) +#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) +#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) +#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) +#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) +#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) +#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) +#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) +#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) +#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) +#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) +#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) +#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) +#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) +#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) +#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) +#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) +#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) +#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) +#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) + +#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; +#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#ifndef JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_USE_IMPLICIT_CONVERSIONS 1 +#endif + +#if JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_EXPLICIT +#else + #define JSON_EXPLICIT explicit +#endif + + +namespace nlohmann +{ +namespace detail +{ +//////////////// +// exceptions // +//////////////// + +/*! +@brief general exception of the @ref basic_json class + +This class is an extension of `std::exception` objects with a member @a id for +exception ids. It is used as the base class for all exceptions thrown by the +@ref basic_json class. This class can hence be used as "wildcard" to catch +exceptions. + +Subclasses: +- @ref parse_error for exceptions indicating a parse error +- @ref invalid_iterator for exceptions indicating errors with iterators +- @ref type_error for exceptions indicating executing a member function with + a wrong type +- @ref out_of_range for exceptions indicating access out of the defined range +- @ref other_error for exceptions indicating other library errors + +@internal +@note To have nothrow-copy-constructible exceptions, we internally use + `std::runtime_error` which can cope with arbitrary-length error messages. + Intermediate strings are built with static functions and then passed to + the actual constructor. +@endinternal + +@liveexample{The following code shows how arbitrary library exceptions can be +caught.,exception} + +@since version 3.0.0 +*/ +class exception : public std::exception +{ + public: + /// returns the explanatory string + JSON_HEDLEY_RETURNS_NON_NULL + const char* what() const noexcept override + { + return m.what(); + } + + /// the id of the exception + const int id; + + protected: + JSON_HEDLEY_NON_NULL(3) + exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} + + static std::string name(const std::string& ename, int id_) + { + return "[json.exception." + ename + "." + std::to_string(id_) + "] "; + } + + private: + /// an exception object as storage for error messages + std::runtime_error m; +}; + +/*! +@brief exception indicating a parse error + +This exception is thrown by the library when a parse error occurs. Parse errors +can occur during the deserialization of JSON text, CBOR, MessagePack, as well +as when using JSON Patch. + +Member @a byte holds the byte index of the last read character in the input +file. + +Exceptions have ids 1xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. +json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. +json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. +json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. +json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. +json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`. +json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. +json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. +json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. +json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. +json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. +json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. +json.exception.parse_error.114 | parse error: Unsupported BSON record type 0x0F | The parsing of the corresponding BSON record type is not implemented (yet). +json.exception.parse_error.115 | parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A | A UBJSON high-precision number could not be parsed. + +@note For an input with n bytes, 1 is the index of the first character and n+1 + is the index of the terminating null byte or the end of file. This also + holds true when reading a byte vector (CBOR or MessagePack). + +@liveexample{The following code shows how a `parse_error` exception can be +caught.,parse_error} + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref invalid_iterator for exceptions indicating errors with iterators +@sa - @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa - @ref out_of_range for exceptions indicating access out of the defined range +@sa - @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class parse_error : public exception +{ + public: + /*! + @brief create a parse error exception + @param[in] id_ the id of the exception + @param[in] pos the position where the error occurred (or with + chars_read_total=0 if the position cannot be + determined) + @param[in] what_arg the explanatory string + @return parse_error object + */ + static parse_error create(int id_, const position_t& pos, const std::string& what_arg) + { + std::string w = exception::name("parse_error", id_) + "parse error" + + position_string(pos) + ": " + what_arg; + return parse_error(id_, pos.chars_read_total, w.c_str()); + } + + static parse_error create(int id_, std::size_t byte_, const std::string& what_arg) + { + std::string w = exception::name("parse_error", id_) + "parse error" + + (byte_ != 0 ? (" at byte " + std::to_string(byte_)) : "") + + ": " + what_arg; + return parse_error(id_, byte_, w.c_str()); + } + + /*! + @brief byte index of the parse error + + The byte index of the last read character in the input file. + + @note For an input with n bytes, 1 is the index of the first character and + n+1 is the index of the terminating null byte or the end of file. + This also holds true when reading a byte vector (CBOR or MessagePack). + */ + const std::size_t byte; + + private: + parse_error(int id_, std::size_t byte_, const char* what_arg) + : exception(id_, what_arg), byte(byte_) {} + + static std::string position_string(const position_t& pos) + { + return " at line " + std::to_string(pos.lines_read + 1) + + ", column " + std::to_string(pos.chars_read_current_line); + } +}; + +/*! +@brief exception indicating errors with iterators + +This exception is thrown if iterators passed to a library function do not match +the expected semantics. + +Exceptions have ids 2xx. + +name / id | example message | description +----------------------------------- | --------------- | ------------------------- +json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. +json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. +json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. +json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. +json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. +json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. +json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. +json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. +json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. +json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). + +@liveexample{The following code shows how an `invalid_iterator` exception can be +caught.,invalid_iterator} + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref parse_error for exceptions indicating a parse error +@sa - @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa - @ref out_of_range for exceptions indicating access out of the defined range +@sa - @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class invalid_iterator : public exception +{ + public: + static invalid_iterator create(int id_, const std::string& what_arg) + { + std::string w = exception::name("invalid_iterator", id_) + what_arg; + return invalid_iterator(id_, w.c_str()); + } + + private: + JSON_HEDLEY_NON_NULL(3) + invalid_iterator(int id_, const char* what_arg) + : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating executing a member function with a wrong type + +This exception is thrown in case of a type error; that is, a library function is +executed on a JSON value whose type does not match the expected semantics. + +Exceptions have ids 3xx. + +name / id | example message | description +----------------------------- | --------------- | ------------------------- +json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. +json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. +json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t &. +json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. +json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. +json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. +json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. +json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. +json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. +json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. +json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. +json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. +json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. +json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. +json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. +json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. | +json.exception.type_error.317 | JSON value cannot be serialized to requested format | The dynamic type of the object cannot be represented in the requested serialization format (e.g. a raw `true` or `null` JSON object cannot be serialized to BSON) | + +@liveexample{The following code shows how a `type_error` exception can be +caught.,type_error} + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref parse_error for exceptions indicating a parse error +@sa - @ref invalid_iterator for exceptions indicating errors with iterators +@sa - @ref out_of_range for exceptions indicating access out of the defined range +@sa - @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class type_error : public exception +{ + public: + static type_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("type_error", id_) + what_arg; + return type_error(id_, w.c_str()); + } + + private: + JSON_HEDLEY_NON_NULL(3) + type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating access out of the defined range + +This exception is thrown in case a library function is called on an input +parameter that exceeds the expected range, for instance in case of array +indices or nonexisting object keys. + +Exceptions have ids 4xx. + +name / id | example message | description +------------------------------- | --------------- | ------------------------- +json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. +json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. +json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. +json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. +json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. +json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. +json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON and BSON only support integer numbers up to 9223372036854775807. (until version 3.8.0) | +json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. | +json.exception.out_of_range.409 | BSON key cannot contain code point U+0000 (at byte 2) | Key identifiers to be serialized to BSON cannot contain code point U+0000, since the key is stored as zero-terminated c-string | + +@liveexample{The following code shows how an `out_of_range` exception can be +caught.,out_of_range} + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref parse_error for exceptions indicating a parse error +@sa - @ref invalid_iterator for exceptions indicating errors with iterators +@sa - @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa - @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class out_of_range : public exception +{ + public: + static out_of_range create(int id_, const std::string& what_arg) + { + std::string w = exception::name("out_of_range", id_) + what_arg; + return out_of_range(id_, w.c_str()); + } + + private: + JSON_HEDLEY_NON_NULL(3) + out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating other library errors + +This exception is thrown in case of errors that cannot be classified with the +other exception types. + +Exceptions have ids 5xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref parse_error for exceptions indicating a parse error +@sa - @ref invalid_iterator for exceptions indicating errors with iterators +@sa - @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa - @ref out_of_range for exceptions indicating access out of the defined range + +@liveexample{The following code shows how an `other_error` exception can be +caught.,other_error} + +@since version 3.0.0 +*/ +class other_error : public exception +{ + public: + static other_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("other_error", id_) + what_arg; + return other_error(id_, w.c_str()); + } + + private: + JSON_HEDLEY_NON_NULL(3) + other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + + +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type + +namespace nlohmann +{ +namespace detail +{ +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +template +using uncvref_t = typename std::remove_cv::type>::type; + +// implementation of C++14 index_sequence and affiliates +// source: https://stackoverflow.com/a/32223343 +template +struct index_sequence +{ + using type = index_sequence; + using value_type = std::size_t; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +template +struct merge_and_renumber; + +template +struct merge_and_renumber, index_sequence> + : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; + +template +struct make_index_sequence + : merge_and_renumber < typename make_index_sequence < N / 2 >::type, + typename make_index_sequence < N - N / 2 >::type > {}; + +template<> struct make_index_sequence<0> : index_sequence<> {}; +template<> struct make_index_sequence<1> : index_sequence<0> {}; + +template +using index_sequence_for = make_index_sequence; + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // numeric_limits +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval + +// #include + + +#include // random_access_iterator_tag + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; +} // namespace detail +} // namespace nlohmann + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template +struct iterator_types {}; + +template +struct iterator_types < + It, + void_t> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template +struct iterator_traits +{ +}; + +template +struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> + : iterator_types +{ +}; + +template +struct iterator_traits::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + +// #include + + +#include + +// #include + + +// https://en.cppreference.com/w/cpp/experimental/is_detected +namespace nlohmann +{ +namespace detail +{ +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template class Op, class... Args> +using is_detected = typename detector::value_t; + +template class Op, class... Args> +using detected_t = typename detector::type; + +template class Op, class... Args> +using detected_or = detector; + +template class Op, class... Args> +using detected_or_t = typename detected_or::type; + +template class Op, class... Args> +using is_detected_exact = std::is_same>; + +template class Op, class... Args> +using is_detected_convertible = + std::is_convertible, To>; +} // namespace detail +} // namespace nlohmann + +// #include +#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ +#define INCLUDE_NLOHMANN_JSON_FWD_HPP_ + +#include // int64_t, uint64_t +#include // map +#include // allocator +#include // string +#include // vector + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer; + +template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer, + class BinaryType = std::vector> +class basic_json; + +/*! +@brief JSON Pointer + +A JSON pointer defines a string syntax for identifying a specific value +within a JSON document. It can be used with functions `at` and +`operator[]`. Furthermore, JSON pointers are the base for JSON patches. + +@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + +@since version 2.0.0 +*/ +template +class json_pointer; + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; + +template +struct ordered_map; + +/*! +@brief ordered JSON class + +This type preserves the insertion order of object keys. + +@since version 3.9.0 +*/ +using ordered_json = basic_json; + +} // namespace nlohmann + +#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + +namespace nlohmann +{ +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. + +@since version 2.1.0 +*/ +namespace detail +{ +///////////// +// helpers // +///////////// + +// Note to maintainers: +// +// Every trait in this file expects a non CV-qualified type. +// The only exceptions are in the 'aliases for detected' section +// (i.e. those of the form: decltype(T::member_function(std::declval()))) +// +// In this case, T has to be properly CV-qualified to constraint the function arguments +// (e.g. to_json(BasicJsonType&, const T&)) + +template struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json : std::true_type {}; + +////////////////////// +// json_ref helpers // +////////////////////// + +template +class json_ref; + +template +struct is_json_ref : std::false_type {}; + +template +struct is_json_ref> : std::true_type {}; + +////////////////////////// +// aliases for detected // +////////////////////////// + +template +using mapped_type_t = typename T::mapped_type; + +template +using key_type_t = typename T::key_type; + +template +using value_type_t = typename T::value_type; + +template +using difference_type_t = typename T::difference_type; + +template +using pointer_t = typename T::pointer; + +template +using reference_t = typename T::reference; + +template +using iterator_category_t = typename T::iterator_category; + +template +using iterator_t = typename T::iterator; + +template +using to_json_function = decltype(T::to_json(std::declval()...)); + +template +using from_json_function = decltype(T::from_json(std::declval()...)); + +template +using get_template_function = decltype(std::declval().template get()); + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json : std::false_type {}; + +// trait checking if j.get is valid +// use this trait instead of std::is_constructible or std::is_convertible, +// both rely on, or make use of implicit conversions, and thus fail when T +// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) +template +struct is_getable +{ + static constexpr bool value = is_detected::value; +}; + +template +struct has_from_json < BasicJsonType, T, + enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json : std::false_type {}; + +template +struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. +template +struct has_to_json : std::false_type {}; + +template +struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + + +/////////////////// +// is_ functions // +/////////////////// + +template +struct is_iterator_traits : std::false_type {}; + +template +struct is_iterator_traits> +{ + private: + using traits = iterator_traits; + + public: + static constexpr auto value = + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value; +}; + +// source: https://stackoverflow.com/a/37193089/4116453 + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl < + BasicJsonType, CompatibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + + using object_t = typename BasicJsonType::object_t; + + // macOS's is_constructible does not play well with nonesuch... + static constexpr bool value = + std::is_constructible::value && + std::is_constructible::value; +}; + +template +struct is_compatible_object_type + : is_compatible_object_type_impl {}; + +template +struct is_constructible_object_type_impl : std::false_type {}; + +template +struct is_constructible_object_type_impl < + BasicJsonType, ConstructibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + static constexpr bool value = + (std::is_default_constructible::value && + (std::is_move_assignable::value || + std::is_copy_assignable::value) && + (std::is_constructible::value && + std::is_same < + typename object_t::mapped_type, + typename ConstructibleObjectType::mapped_type >::value)) || + (has_from_json::value || + has_non_default_from_json < + BasicJsonType, + typename ConstructibleObjectType::mapped_type >::value); +}; + +template +struct is_constructible_object_type + : is_constructible_object_type_impl {}; + +template +struct is_compatible_string_type_impl : std::false_type {}; + +template +struct is_compatible_string_type_impl < + BasicJsonType, CompatibleStringType, + enable_if_t::value >> +{ + static constexpr auto value = + std::is_constructible::value; +}; + +template +struct is_compatible_string_type + : is_compatible_string_type_impl {}; + +template +struct is_constructible_string_type_impl : std::false_type {}; + +template +struct is_constructible_string_type_impl < + BasicJsonType, ConstructibleStringType, + enable_if_t::value >> +{ + static constexpr auto value = + std::is_constructible::value; +}; + +template +struct is_constructible_string_type + : is_constructible_string_type_impl {}; + +template +struct is_compatible_array_type_impl : std::false_type {}; + +template +struct is_compatible_array_type_impl < + BasicJsonType, CompatibleArrayType, + enable_if_t < is_detected::value&& + is_detected::value&& +// This is needed because json_reverse_iterator has a ::iterator type... +// Therefore it is detected as a CompatibleArrayType. +// The real fix would be to have an Iterable concept. + !is_iterator_traits < + iterator_traits>::value >> +{ + static constexpr bool value = + std::is_constructible::value; +}; + +template +struct is_compatible_array_type + : is_compatible_array_type_impl {}; + +template +struct is_constructible_array_type_impl : std::false_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t::value >> + : std::true_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t < !std::is_same::value&& + std::is_default_constructible::value&& +(std::is_move_assignable::value || + std::is_copy_assignable::value)&& +is_detected::value&& +is_detected::value&& +is_complete_type < +detected_t>::value >> +{ + static constexpr bool value = + // This is needed because json_reverse_iterator has a ::iterator type, + // furthermore, std::back_insert_iterator (and other iterators) have a + // base class `iterator`... Therefore it is detected as a + // ConstructibleArrayType. The real fix would be to have an Iterable + // concept. + !is_iterator_traits>::value && + + (std::is_same::value || + has_from_json::value || + has_non_default_from_json < + BasicJsonType, typename ConstructibleArrayType::value_type >::value); +}; + +template +struct is_constructible_array_type + : is_constructible_array_type_impl {}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl < + RealIntegerType, CompatibleNumberIntegerType, + enable_if_t < std::is_integral::value&& + std::is_integral::value&& + !std::is_same::value >> +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + std::is_constructible::value && + CompatibleLimits::is_integer && + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type + : is_compatible_integer_type_impl {}; + +template +struct is_compatible_type_impl: std::false_type {}; + +template +struct is_compatible_type_impl < + BasicJsonType, CompatibleType, + enable_if_t::value >> +{ + static constexpr bool value = + has_to_json::value; +}; + +template +struct is_compatible_type + : is_compatible_type_impl {}; + +// https://en.cppreference.com/w/cpp/types/conjunction +template struct conjunction : std::true_type { }; +template struct conjunction : B1 { }; +template +struct conjunction +: std::conditional, B1>::type {}; + +template +struct is_constructible_tuple : std::false_type {}; + +template +struct is_constructible_tuple> : conjunction...> {}; +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // array +#include // size_t +#include // uint8_t +#include // string + +namespace nlohmann +{ +namespace detail +{ +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +} +} // namespace detail +} // namespace nlohmann + + +namespace nlohmann +{ +namespace detail +{ +template +void from_json(const BasicJsonType& j, typename std::nullptr_t& n) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_null())) + { + JSON_THROW(type_error::create(302, "type must be null, but is " + std::string(j.type_name()))); + } + n = nullptr; +} + +// overloads for basic_json template parameters +template < typename BasicJsonType, typename ArithmeticType, + enable_if_t < std::is_arithmetic::value&& + !std::is_same::value, + int > = 0 > +void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) + { + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); + } + b = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_string())) + { + JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); + } + s = *j.template get_ptr(); +} + +template < + typename BasicJsonType, typename ConstructibleStringType, + enable_if_t < + is_constructible_string_type::value&& + !std::is_same::value, + int > = 0 > +void from_json(const BasicJsonType& j, ConstructibleStringType& s) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_string())) + { + JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); + } + + s = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +{ + get_arithmetic_value(j, val); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, EnumType& e) +{ + typename std::underlying_type::type val; + get_arithmetic_value(j, val); + e = static_cast(val); +} + +// forward_list doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::forward_list& l) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + l.clear(); + std::transform(j.rbegin(), j.rend(), + std::front_inserter(l), [](const BasicJsonType & i) + { + return i.template get(); + }); +} + +// valarray doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::valarray& l) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + l.resize(j.size()); + std::transform(j.begin(), j.end(), std::begin(l), + [](const BasicJsonType & elem) + { + return elem.template get(); + }); +} + +template +auto from_json(const BasicJsonType& j, T (&arr)[N]) +-> decltype(j.template get(), void()) +{ + for (std::size_t i = 0; i < N; ++i) + { + arr[i] = j.at(i).template get(); + } +} + +template +void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) +{ + arr = *j.template get_ptr(); +} + +template +auto from_json_array_impl(const BasicJsonType& j, std::array& arr, + priority_tag<2> /*unused*/) +-> decltype(j.template get(), void()) +{ + for (std::size_t i = 0; i < N; ++i) + { + arr[i] = j.at(i).template get(); + } +} + +template +auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/) +-> decltype( + arr.reserve(std::declval()), + j.template get(), + void()) +{ + using std::end; + + ConstructibleArrayType ret; + ret.reserve(j.size()); + std::transform(j.begin(), j.end(), + std::inserter(ret, end(ret)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); + arr = std::move(ret); +} + +template +void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, + priority_tag<0> /*unused*/) +{ + using std::end; + + ConstructibleArrayType ret; + std::transform( + j.begin(), j.end(), std::inserter(ret, end(ret)), + [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); + arr = std::move(ret); +} + +template < typename BasicJsonType, typename ConstructibleArrayType, + enable_if_t < + is_constructible_array_type::value&& + !is_constructible_object_type::value&& + !is_constructible_string_type::value&& + !std::is_same::value&& + !is_basic_json::value, + int > = 0 > +auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) +-> decltype(from_json_array_impl(j, arr, priority_tag<3> {}), +j.template get(), +void()) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + + std::string(j.type_name()))); + } + + from_json_array_impl(j, arr, priority_tag<3> {}); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) + { + JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(j.type_name()))); + } + + bin = *j.template get_ptr(); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_object())) + { + JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); + } + + ConstructibleObjectType ret; + auto inner_object = j.template get_ptr(); + using value_type = typename ConstructibleObjectType::value_type; + std::transform( + inner_object->begin(), inner_object->end(), + std::inserter(ret, ret.begin()), + [](typename BasicJsonType::object_t::value_type const & p) + { + return value_type(p.first, p.second.template get()); + }); + obj = std::move(ret); +} + +// overload for arithmetic types, not chosen for basic_json template arguments +// (BooleanType, etc..); note: Is it really necessary to provide explicit +// overloads for boolean_t etc. in case of a custom BooleanType which is not +// an arithmetic type? +template < typename BasicJsonType, typename ArithmeticType, + enable_if_t < + std::is_arithmetic::value&& + !std::is_same::value&& + !std::is_same::value&& + !std::is_same::value&& + !std::is_same::value, + int > = 0 > +void from_json(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::boolean: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template +void from_json(const BasicJsonType& j, std::pair& p) +{ + p = {j.at(0).template get(), j.at(1).template get()}; +} + +template +void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence /*unused*/) +{ + t = std::make_tuple(j.at(Idx).template get::type>()...); +} + +template +void from_json(const BasicJsonType& j, std::tuple& t) +{ + from_json_tuple_impl(j, t, index_sequence_for {}); +} + +template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, + typename = enable_if_t < !std::is_constructible < + typename BasicJsonType::string_t, Key >::value >> +void from_json(const BasicJsonType& j, std::map& m) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + m.clear(); + for (const auto& p : j) + { + if (JSON_HEDLEY_UNLIKELY(!p.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()))); + } + m.emplace(p.at(0).template get(), p.at(1).template get()); + } +} + +template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, + typename = enable_if_t < !std::is_constructible < + typename BasicJsonType::string_t, Key >::value >> +void from_json(const BasicJsonType& j, std::unordered_map& m) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + m.clear(); + for (const auto& p : j) + { + if (JSON_HEDLEY_UNLIKELY(!p.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()))); + } + m.emplace(p.at(0).template get(), p.at(1).template get()); + } +} + +struct from_json_fn +{ + template + auto operator()(const BasicJsonType& j, T& val) const + noexcept(noexcept(from_json(j, val))) + -> decltype(from_json(j, val), void()) + { + return from_json(j, val); + } +}; +} // namespace detail + +/// namespace to hold default `from_json` function +/// to see why this is required: +/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html +namespace +{ +constexpr const auto& from_json = detail::static_const::value; +} // namespace +} // namespace nlohmann + +// #include + + +#include // copy +#include // begin, end +#include // string +#include // tuple, get +#include // is_same, is_constructible, is_floating_point, is_enum, underlying_type +#include // move, forward, declval, pair +#include // valarray +#include // vector + +// #include + + +#include // size_t +#include // input_iterator_tag +#include // string, to_string +#include // tuple_size, get, tuple_element + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template +void int_to_string( string_type& target, std::size_t value ) +{ + // For ADL + using std::to_string; + target = to_string(value); +} +template class iteration_proxy_value +{ + public: + using difference_type = std::ptrdiff_t; + using value_type = iteration_proxy_value; + using pointer = value_type * ; + using reference = value_type & ; + using iterator_category = std::input_iterator_tag; + using string_type = typename std::remove_cv< typename std::remove_reference().key() ) >::type >::type; + + private: + /// the iterator + IteratorType anchor; + /// an index for arrays (used to create key names) + std::size_t array_index = 0; + /// last stringified array index + mutable std::size_t array_index_last = 0; + /// a string representation of the array index + mutable string_type array_index_str = "0"; + /// an empty string (to return a reference for primitive values) + const string_type empty_str = ""; + + public: + explicit iteration_proxy_value(IteratorType it) noexcept : anchor(it) {} + + /// dereference operator (needed for range-based for) + iteration_proxy_value& operator*() + { + return *this; + } + + /// increment operator (needed for range-based for) + iteration_proxy_value& operator++() + { + ++anchor; + ++array_index; + + return *this; + } + + /// equality operator (needed for InputIterator) + bool operator==(const iteration_proxy_value& o) const + { + return anchor == o.anchor; + } + + /// inequality operator (needed for range-based for) + bool operator!=(const iteration_proxy_value& o) const + { + return anchor != o.anchor; + } + + /// return key of the iterator + const string_type& key() const + { + JSON_ASSERT(anchor.m_object != nullptr); + + switch (anchor.m_object->type()) + { + // use integer array index as key + case value_t::array: + { + if (array_index != array_index_last) + { + int_to_string( array_index_str, array_index ); + array_index_last = array_index; + } + return array_index_str; + } + + // use key from the object + case value_t::object: + return anchor.key(); + + // use an empty key for all primitive types + default: + return empty_str; + } + } + + /// return value of the iterator + typename IteratorType::reference value() const + { + return anchor.value(); + } +}; + +/// proxy class for the items() function +template class iteration_proxy +{ + private: + /// the container to iterate + typename IteratorType::reference container; + + public: + /// construct iteration proxy from a container + explicit iteration_proxy(typename IteratorType::reference cont) noexcept + : container(cont) {} + + /// return iterator begin (needed for range-based for) + iteration_proxy_value begin() noexcept + { + return iteration_proxy_value(container.begin()); + } + + /// return iterator end (needed for range-based for) + iteration_proxy_value end() noexcept + { + return iteration_proxy_value(container.end()); + } +}; +// Structured Bindings Support +// For further reference see https://blog.tartanllama.xyz/structured-bindings/ +// And see https://github.com/nlohmann/json/pull/1391 +template = 0> +auto get(const nlohmann::detail::iteration_proxy_value& i) -> decltype(i.key()) +{ + return i.key(); +} +// Structured Bindings Support +// For further reference see https://blog.tartanllama.xyz/structured-bindings/ +// And see https://github.com/nlohmann/json/pull/1391 +template = 0> +auto get(const nlohmann::detail::iteration_proxy_value& i) -> decltype(i.value()) +{ + return i.value(); +} +} // namespace detail +} // namespace nlohmann + +// The Addition to the STD Namespace is required to add +// Structured Bindings Support to the iteration_proxy_value class +// For further reference see https://blog.tartanllama.xyz/structured-bindings/ +// And see https://github.com/nlohmann/json/pull/1391 +namespace std +{ +#if defined(__clang__) + // Fix: https://github.com/nlohmann/json/issues/1401 + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wmismatched-tags" +#endif +template +class tuple_size<::nlohmann::detail::iteration_proxy_value> + : public std::integral_constant {}; + +template +class tuple_element> +{ + public: + using type = decltype( + get(std::declval < + ::nlohmann::detail::iteration_proxy_value> ())); +}; +#if defined(__clang__) + #pragma clang diagnostic pop +#endif +} // namespace std + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +////////////////// +// constructors // +////////////////// + +template struct external_constructor; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept + { + j.m_type = value_t::boolean; + j.m_value = b; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) + { + j.m_type = value_t::string; + j.m_value = s; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) + { + j.m_type = value_t::string; + j.m_value = std::move(s); + j.assert_invariant(); + } + + template < typename BasicJsonType, typename CompatibleStringType, + enable_if_t < !std::is_same::value, + int > = 0 > + static void construct(BasicJsonType& j, const CompatibleStringType& str) + { + j.m_type = value_t::string; + j.m_value.string = j.template create(str); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b) + { + j.m_type = value_t::binary; + typename BasicJsonType::binary_t value{b}; + j.m_value = value; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b) + { + j.m_type = value_t::binary; + typename BasicJsonType::binary_t value{std::move(b)}; + j.m_value = value; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept + { + j.m_type = value_t::number_float; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept + { + j.m_type = value_t::number_unsigned; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept + { + j.m_type = value_t::number_integer; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) + { + j.m_type = value_t::array; + j.m_value = arr; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) + { + j.m_type = value_t::array; + j.m_value = std::move(arr); + j.assert_invariant(); + } + + template < typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < !std::is_same::value, + int > = 0 > + static void construct(BasicJsonType& j, const CompatibleArrayType& arr) + { + using std::begin; + using std::end; + j.m_type = value_t::array; + j.m_value.array = j.template create(begin(arr), end(arr)); + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, const std::vector& arr) + { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->reserve(arr.size()); + for (const bool x : arr) + { + j.m_value.array->push_back(x); + } + j.assert_invariant(); + } + + template::value, int> = 0> + static void construct(BasicJsonType& j, const std::valarray& arr) + { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->resize(arr.size()); + if (arr.size() > 0) + { + std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); + } + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) + { + j.m_type = value_t::object; + j.m_value = obj; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) + { + j.m_type = value_t::object; + j.m_value = std::move(obj); + j.assert_invariant(); + } + + template < typename BasicJsonType, typename CompatibleObjectType, + enable_if_t < !std::is_same::value, int > = 0 > + static void construct(BasicJsonType& j, const CompatibleObjectType& obj) + { + using std::begin; + using std::end; + + j.m_type = value_t::object; + j.m_value.object = j.template create(begin(obj), end(obj)); + j.assert_invariant(); + } +}; + +///////////// +// to_json // +///////////// + +template::value, int> = 0> +void to_json(BasicJsonType& j, T b) noexcept +{ + external_constructor::construct(j, b); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, const CompatibleString& s) +{ + external_constructor::construct(j, s); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) +{ + external_constructor::construct(j, std::move(s)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, FloatType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, EnumType e) noexcept +{ + using underlying_type = typename std::underlying_type::type; + external_constructor::construct(j, static_cast(e)); +} + +template +void to_json(BasicJsonType& j, const std::vector& e) +{ + external_constructor::construct(j, e); +} + +template < typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < is_compatible_array_type::value&& + !is_compatible_object_type::value&& + !is_compatible_string_type::value&& + !std::is_same::value&& + !is_basic_json::value, + int > = 0 > +void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +{ + external_constructor::construct(j, arr); +} + +template +void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) +{ + external_constructor::construct(j, bin); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, const std::valarray& arr) +{ + external_constructor::construct(j, std::move(arr)); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) +{ + external_constructor::construct(j, std::move(arr)); +} + +template < typename BasicJsonType, typename CompatibleObjectType, + enable_if_t < is_compatible_object_type::value&& !is_basic_json::value, int > = 0 > +void to_json(BasicJsonType& j, const CompatibleObjectType& obj) +{ + external_constructor::construct(j, obj); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) +{ + external_constructor::construct(j, std::move(obj)); +} + +template < + typename BasicJsonType, typename T, std::size_t N, + enable_if_t < !std::is_constructible::value, + int > = 0 > +void to_json(BasicJsonType& j, const T(&arr)[N]) +{ + external_constructor::construct(j, arr); +} + +template < typename BasicJsonType, typename T1, typename T2, enable_if_t < std::is_constructible::value&& std::is_constructible::value, int > = 0 > +void to_json(BasicJsonType& j, const std::pair& p) +{ + j = { p.first, p.second }; +} + +// for https://github.com/nlohmann/json/pull/1134 +template>::value, int> = 0> +void to_json(BasicJsonType& j, const T& b) +{ + j = { {b.key(), b.value()} }; +} + +template +void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence /*unused*/) +{ + j = { std::get(t)... }; +} + +template::value, int > = 0> +void to_json(BasicJsonType& j, const T& t) +{ + to_json_tuple_impl(j, t, make_index_sequence::value> {}); +} + +struct to_json_fn +{ + template + auto operator()(BasicJsonType& j, T&& val) const noexcept(noexcept(to_json(j, std::forward(val)))) + -> decltype(to_json(j, std::forward(val)), void()) + { + return to_json(j, std::forward(val)); + } +}; +} // namespace detail + +/// namespace to hold default `to_json` function +namespace +{ +constexpr const auto& to_json = detail::static_const::value; +} // namespace +} // namespace nlohmann + + +namespace nlohmann +{ + +template +struct adl_serializer +{ + /*! + @brief convert a JSON value to any value type + + This function is usually called by the `get()` function of the + @ref basic_json class (either explicit or via conversion operators). + + @param[in] j JSON value to read from + @param[in,out] val value to write to + */ + template + static auto from_json(BasicJsonType&& j, ValueType& val) noexcept( + noexcept(::nlohmann::from_json(std::forward(j), val))) + -> decltype(::nlohmann::from_json(std::forward(j), val), void()) + { + ::nlohmann::from_json(std::forward(j), val); + } + + /*! + @brief convert any value type to a JSON value + + This function is usually called by the constructors of the @ref basic_json + class. + + @param[in,out] j JSON value to write to + @param[in] val value to read from + */ + template + static auto to_json(BasicJsonType& j, ValueType&& val) noexcept( + noexcept(::nlohmann::to_json(j, std::forward(val)))) + -> decltype(::nlohmann::to_json(j, std::forward(val)), void()) + { + ::nlohmann::to_json(j, std::forward(val)); + } +}; + +} // namespace nlohmann + +// #include + + +#include // uint8_t +#include // tie +#include // move + +namespace nlohmann +{ + +/*! +@brief an internal type for a backed binary type + +This type extends the template parameter @a BinaryType provided to `basic_json` +with a subtype used by BSON and MessagePack. This type exists so that the user +does not have to specify a type themselves with a specific naming scheme in +order to override the binary type. + +@tparam BinaryType container to store bytes (`std::vector` by + default) + +@since version 3.8.0 +*/ +template +class byte_container_with_subtype : public BinaryType +{ + public: + /// the type of the underlying container + using container_type = BinaryType; + + byte_container_with_subtype() noexcept(noexcept(container_type())) + : container_type() + {} + + byte_container_with_subtype(const container_type& b) noexcept(noexcept(container_type(b))) + : container_type(b) + {} + + byte_container_with_subtype(container_type&& b) noexcept(noexcept(container_type(std::move(b)))) + : container_type(std::move(b)) + {} + + byte_container_with_subtype(const container_type& b, std::uint8_t subtype) noexcept(noexcept(container_type(b))) + : container_type(b) + , m_subtype(subtype) + , m_has_subtype(true) + {} + + byte_container_with_subtype(container_type&& b, std::uint8_t subtype) noexcept(noexcept(container_type(std::move(b)))) + : container_type(std::move(b)) + , m_subtype(subtype) + , m_has_subtype(true) + {} + + bool operator==(const byte_container_with_subtype& rhs) const + { + return std::tie(static_cast(*this), m_subtype, m_has_subtype) == + std::tie(static_cast(rhs), rhs.m_subtype, rhs.m_has_subtype); + } + + bool operator!=(const byte_container_with_subtype& rhs) const + { + return !(rhs == *this); + } + + /*! + @brief sets the binary subtype + + Sets the binary subtype of the value, also flags a binary JSON value as + having a subtype, which has implications for serialization. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @sa @ref subtype() -- return the binary subtype + @sa @ref clear_subtype() -- clears the binary subtype + @sa @ref has_subtype() -- returns whether or not the binary value has a + subtype + + @since version 3.8.0 + */ + void set_subtype(std::uint8_t subtype) noexcept + { + m_subtype = subtype; + m_has_subtype = true; + } + + /*! + @brief return the binary subtype + + Returns the numerical subtype of the value if it has a subtype. If it does + not have a subtype, this function will return size_t(-1) as a sentinel + value. + + @return the numerical subtype of the binary value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @sa @ref set_subtype() -- sets the binary subtype + @sa @ref clear_subtype() -- clears the binary subtype + @sa @ref has_subtype() -- returns whether or not the binary value has a + subtype + + @since version 3.8.0 + */ + constexpr std::uint8_t subtype() const noexcept + { + return m_subtype; + } + + /*! + @brief return whether the value has a subtype + + @return whether the value has a subtype + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @sa @ref subtype() -- return the binary subtype + @sa @ref set_subtype() -- sets the binary subtype + @sa @ref clear_subtype() -- clears the binary subtype + + @since version 3.8.0 + */ + constexpr bool has_subtype() const noexcept + { + return m_has_subtype; + } + + /*! + @brief clears the binary subtype + + Clears the binary subtype and flags the value as not having a subtype, which + has implications for serialization; for instance MessagePack will prefer the + bin family over the ext family. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @sa @ref subtype() -- return the binary subtype + @sa @ref set_subtype() -- sets the binary subtype + @sa @ref has_subtype() -- returns whether or not the binary value has a + subtype + + @since version 3.8.0 + */ + void clear_subtype() noexcept + { + m_subtype = 0; + m_has_subtype = false; + } + + private: + std::uint8_t m_subtype = 0; + bool m_has_subtype = false; +}; + +} // namespace nlohmann + +// #include + +// #include + +// #include + +// #include + + +#include // size_t, uint8_t +#include // hash + +namespace nlohmann +{ +namespace detail +{ + +// boost::hash_combine +inline std::size_t combine(std::size_t seed, std::size_t h) noexcept +{ + seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U); + return seed; +} + +/*! +@brief hash a JSON value + +The hash function tries to rely on std::hash where possible. Furthermore, the +type of the JSON value is taken into account to have different hash values for +null, 0, 0U, and false, etc. + +@tparam BasicJsonType basic_json specialization +@param j JSON value to hash +@return hash value of j +*/ +template +std::size_t hash(const BasicJsonType& j) +{ + using string_t = typename BasicJsonType::string_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + + const auto type = static_cast(j.type()); + switch (j.type()) + { + case BasicJsonType::value_t::null: + case BasicJsonType::value_t::discarded: + { + return combine(type, 0); + } + + case BasicJsonType::value_t::object: + { + auto seed = combine(type, j.size()); + for (const auto& element : j.items()) + { + const auto h = std::hash {}(element.key()); + seed = combine(seed, h); + seed = combine(seed, hash(element.value())); + } + return seed; + } + + case BasicJsonType::value_t::array: + { + auto seed = combine(type, j.size()); + for (const auto& element : j) + { + seed = combine(seed, hash(element)); + } + return seed; + } + + case BasicJsonType::value_t::string: + { + const auto h = std::hash {}(j.template get_ref()); + return combine(type, h); + } + + case BasicJsonType::value_t::boolean: + { + const auto h = std::hash {}(j.template get()); + return combine(type, h); + } + + case BasicJsonType::value_t::number_integer: + { + const auto h = std::hash {}(j.template get()); + return combine(type, h); + } + + case nlohmann::detail::value_t::number_unsigned: + { + const auto h = std::hash {}(j.template get()); + return combine(type, h); + } + + case nlohmann::detail::value_t::number_float: + { + const auto h = std::hash {}(j.template get()); + return combine(type, h); + } + + case nlohmann::detail::value_t::binary: + { + auto seed = combine(type, j.get_binary().size()); + const auto h = std::hash {}(j.get_binary().has_subtype()); + seed = combine(seed, h); + seed = combine(seed, j.get_binary().subtype()); + for (const auto byte : j.get_binary()) + { + seed = combine(seed, std::hash {}(byte)); + } + return seed; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } +} + +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // generate_n +#include // array +#include // ldexp +#include // size_t +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // snprintf +#include // memcpy +#include // back_inserter +#include // numeric_limits +#include // char_traits, string +#include // make_pair, move + +// #include + +// #include + + +#include // array +#include // size_t +#include //FILE * +#include // strlen +#include // istream +#include // begin, end, iterator_traits, random_access_iterator_tag, distance, next +#include // shared_ptr, make_shared, addressof +#include // accumulate +#include // string, char_traits +#include // enable_if, is_base_of, is_pointer, is_integral, remove_pointer +#include // pair, declval + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/// the supported input formats +enum class input_format_t { json, cbor, msgpack, ubjson, bson }; + +//////////////////// +// input adapters // +//////////////////// + +/*! +Input adapter for stdio file access. This adapter read only 1 byte and do not use any + buffer. This adapter is a very low level adapter. +*/ +class file_input_adapter +{ + public: + using char_type = char; + + JSON_HEDLEY_NON_NULL(2) + explicit file_input_adapter(std::FILE* f) noexcept + : m_file(f) + {} + + // make class move-only + file_input_adapter(const file_input_adapter&) = delete; + file_input_adapter(file_input_adapter&&) = default; + file_input_adapter& operator=(const file_input_adapter&) = delete; + file_input_adapter& operator=(file_input_adapter&&) = delete; + + std::char_traits::int_type get_character() noexcept + { + return std::fgetc(m_file); + } + + private: + /// the file pointer to read from + std::FILE* m_file; +}; + + +/*! +Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at +beginning of input. Does not support changing the underlying std::streambuf +in mid-input. Maintains underlying std::istream and std::streambuf to support +subsequent use of standard std::istream operations to process any input +characters following those used in parsing the JSON input. Clears the +std::istream flags; any input errors (e.g., EOF) will be detected by the first +subsequent call for input from the std::istream. +*/ +class input_stream_adapter +{ + public: + using char_type = char; + + ~input_stream_adapter() + { + // clear stream flags; we use underlying streambuf I/O, do not + // maintain ifstream flags, except eof + if (is != nullptr) + { + is->clear(is->rdstate() & std::ios::eofbit); + } + } + + explicit input_stream_adapter(std::istream& i) + : is(&i), sb(i.rdbuf()) + {} + + // delete because of pointer members + input_stream_adapter(const input_stream_adapter&) = delete; + input_stream_adapter& operator=(input_stream_adapter&) = delete; + input_stream_adapter& operator=(input_stream_adapter&& rhs) = delete; + + input_stream_adapter(input_stream_adapter&& rhs) noexcept : is(rhs.is), sb(rhs.sb) + { + rhs.is = nullptr; + rhs.sb = nullptr; + } + + // std::istream/std::streambuf use std::char_traits::to_int_type, to + // ensure that std::char_traits::eof() and the character 0xFF do not + // end up as the same value, eg. 0xFFFFFFFF. + std::char_traits::int_type get_character() + { + auto res = sb->sbumpc(); + // set eof manually, as we don't use the istream interface. + if (JSON_HEDLEY_UNLIKELY(res == EOF)) + { + is->clear(is->rdstate() | std::ios::eofbit); + } + return res; + } + + private: + /// the associated input stream + std::istream* is = nullptr; + std::streambuf* sb = nullptr; +}; + +// General-purpose iterator-based adapter. It might not be as fast as +// theoretically possible for some containers, but it is extremely versatile. +template +class iterator_input_adapter +{ + public: + using char_type = typename std::iterator_traits::value_type; + + iterator_input_adapter(IteratorType first, IteratorType last) + : current(std::move(first)), end(std::move(last)) {} + + typename std::char_traits::int_type get_character() + { + if (JSON_HEDLEY_LIKELY(current != end)) + { + auto result = std::char_traits::to_int_type(*current); + std::advance(current, 1); + return result; + } + else + { + return std::char_traits::eof(); + } + } + + private: + IteratorType current; + IteratorType end; + + template + friend struct wide_string_input_helper; + + bool empty() const + { + return current == end; + } + +}; + + +template +struct wide_string_input_helper; + +template +struct wide_string_input_helper +{ + // UTF-32 + static void fill_buffer(BaseInputAdapter& input, + std::array::int_type, 4>& utf8_bytes, + size_t& utf8_bytes_index, + size_t& utf8_bytes_filled) + { + utf8_bytes_index = 0; + + if (JSON_HEDLEY_UNLIKELY(input.empty())) + { + utf8_bytes[0] = std::char_traits::eof(); + utf8_bytes_filled = 1; + } + else + { + // get the current character + const auto wc = input.get_character(); + + // UTF-32 to UTF-8 encoding + if (wc < 0x80) + { + utf8_bytes[0] = static_cast::int_type>(wc); + utf8_bytes_filled = 1; + } + else if (wc <= 0x7FF) + { + utf8_bytes[0] = static_cast::int_type>(0xC0u | ((static_cast(wc) >> 6u) & 0x1Fu)); + utf8_bytes[1] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); + utf8_bytes_filled = 2; + } + else if (wc <= 0xFFFF) + { + utf8_bytes[0] = static_cast::int_type>(0xE0u | ((static_cast(wc) >> 12u) & 0x0Fu)); + utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); + utf8_bytes[2] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); + utf8_bytes_filled = 3; + } + else if (wc <= 0x10FFFF) + { + utf8_bytes[0] = static_cast::int_type>(0xF0u | ((static_cast(wc) >> 18u) & 0x07u)); + utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 12u) & 0x3Fu)); + utf8_bytes[2] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); + utf8_bytes[3] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); + utf8_bytes_filled = 4; + } + else + { + // unknown character + utf8_bytes[0] = static_cast::int_type>(wc); + utf8_bytes_filled = 1; + } + } + } +}; + +template +struct wide_string_input_helper +{ + // UTF-16 + static void fill_buffer(BaseInputAdapter& input, + std::array::int_type, 4>& utf8_bytes, + size_t& utf8_bytes_index, + size_t& utf8_bytes_filled) + { + utf8_bytes_index = 0; + + if (JSON_HEDLEY_UNLIKELY(input.empty())) + { + utf8_bytes[0] = std::char_traits::eof(); + utf8_bytes_filled = 1; + } + else + { + // get the current character + const auto wc = input.get_character(); + + // UTF-16 to UTF-8 encoding + if (wc < 0x80) + { + utf8_bytes[0] = static_cast::int_type>(wc); + utf8_bytes_filled = 1; + } + else if (wc <= 0x7FF) + { + utf8_bytes[0] = static_cast::int_type>(0xC0u | ((static_cast(wc) >> 6u))); + utf8_bytes[1] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); + utf8_bytes_filled = 2; + } + else if (0xD800 > wc || wc >= 0xE000) + { + utf8_bytes[0] = static_cast::int_type>(0xE0u | ((static_cast(wc) >> 12u))); + utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); + utf8_bytes[2] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); + utf8_bytes_filled = 3; + } + else + { + if (JSON_HEDLEY_UNLIKELY(!input.empty())) + { + const auto wc2 = static_cast(input.get_character()); + const auto charcode = 0x10000u + (((static_cast(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu)); + utf8_bytes[0] = static_cast::int_type>(0xF0u | (charcode >> 18u)); + utf8_bytes[1] = static_cast::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu)); + utf8_bytes[2] = static_cast::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu)); + utf8_bytes[3] = static_cast::int_type>(0x80u | (charcode & 0x3Fu)); + utf8_bytes_filled = 4; + } + else + { + utf8_bytes[0] = static_cast::int_type>(wc); + utf8_bytes_filled = 1; + } + } + } + } +}; + +// Wraps another input apdater to convert wide character types into individual bytes. +template +class wide_string_input_adapter +{ + public: + using char_type = char; + + wide_string_input_adapter(BaseInputAdapter base) + : base_adapter(base) {} + + typename std::char_traits::int_type get_character() noexcept + { + // check if buffer needs to be filled + if (utf8_bytes_index == utf8_bytes_filled) + { + fill_buffer(); + + JSON_ASSERT(utf8_bytes_filled > 0); + JSON_ASSERT(utf8_bytes_index == 0); + } + + // use buffer + JSON_ASSERT(utf8_bytes_filled > 0); + JSON_ASSERT(utf8_bytes_index < utf8_bytes_filled); + return utf8_bytes[utf8_bytes_index++]; + } + + private: + BaseInputAdapter base_adapter; + + template + void fill_buffer() + { + wide_string_input_helper::fill_buffer(base_adapter, utf8_bytes, utf8_bytes_index, utf8_bytes_filled); + } + + /// a buffer for UTF-8 bytes + std::array::int_type, 4> utf8_bytes = {{0, 0, 0, 0}}; + + /// index to the utf8_codes array for the next valid byte + std::size_t utf8_bytes_index = 0; + /// number of valid bytes in the utf8_codes array + std::size_t utf8_bytes_filled = 0; +}; + + +template +struct iterator_input_adapter_factory +{ + using iterator_type = IteratorType; + using char_type = typename std::iterator_traits::value_type; + using adapter_type = iterator_input_adapter; + + static adapter_type create(IteratorType first, IteratorType last) + { + return adapter_type(std::move(first), std::move(last)); + } +}; + +template +struct is_iterator_of_multibyte +{ + using value_type = typename std::iterator_traits::value_type; + enum + { + value = sizeof(value_type) > 1 + }; +}; + +template +struct iterator_input_adapter_factory::value>> +{ + using iterator_type = IteratorType; + using char_type = typename std::iterator_traits::value_type; + using base_adapter_type = iterator_input_adapter; + using adapter_type = wide_string_input_adapter; + + static adapter_type create(IteratorType first, IteratorType last) + { + return adapter_type(base_adapter_type(std::move(first), std::move(last))); + } +}; + +// General purpose iterator-based input +template +typename iterator_input_adapter_factory::adapter_type input_adapter(IteratorType first, IteratorType last) +{ + using factory_type = iterator_input_adapter_factory; + return factory_type::create(first, last); +} + +// Convenience shorthand from container to iterator +template +auto input_adapter(const ContainerType& container) -> decltype(input_adapter(begin(container), end(container))) +{ + // Enable ADL + using std::begin; + using std::end; + + return input_adapter(begin(container), end(container)); +} + +// Special cases with fast paths +inline file_input_adapter input_adapter(std::FILE* file) +{ + return file_input_adapter(file); +} + +inline input_stream_adapter input_adapter(std::istream& stream) +{ + return input_stream_adapter(stream); +} + +inline input_stream_adapter input_adapter(std::istream&& stream) +{ + return input_stream_adapter(stream); +} + +using contiguous_bytes_input_adapter = decltype(input_adapter(std::declval(), std::declval())); + +// Null-delimited strings, and the like. +template < typename CharT, + typename std::enable_if < + std::is_pointer::value&& + !std::is_array::value&& + std::is_integral::type>::value&& + sizeof(typename std::remove_pointer::type) == 1, + int >::type = 0 > +contiguous_bytes_input_adapter input_adapter(CharT b) +{ + auto length = std::strlen(reinterpret_cast(b)); + const auto* ptr = reinterpret_cast(b); + return input_adapter(ptr, ptr + length); +} + +template +auto input_adapter(T (&array)[N]) -> decltype(input_adapter(array, array + N)) +{ + return input_adapter(array, array + N); +} + +// This class only handles inputs of input_buffer_adapter type. +// It's required so that expressions like {ptr, len} can be implicitely casted +// to the correct adapter. +class span_input_adapter +{ + public: + template < typename CharT, + typename std::enable_if < + std::is_pointer::value&& + std::is_integral::type>::value&& + sizeof(typename std::remove_pointer::type) == 1, + int >::type = 0 > + span_input_adapter(CharT b, std::size_t l) + : ia(reinterpret_cast(b), reinterpret_cast(b) + l) {} + + template::iterator_category, std::random_access_iterator_tag>::value, + int>::type = 0> + span_input_adapter(IteratorType first, IteratorType last) + : ia(input_adapter(first, last)) {} + + contiguous_bytes_input_adapter&& get() + { + return std::move(ia); + } + + private: + contiguous_bytes_input_adapter ia; +}; +} // namespace detail +} // namespace nlohmann + +// #include + + +#include +#include // string +#include // move +#include // vector + +// #include + +// #include + + +namespace nlohmann +{ + +/*! +@brief SAX interface + +This class describes the SAX interface used by @ref nlohmann::json::sax_parse. +Each function is called in different situations while the input is parsed. The +boolean return value informs the parser whether to continue processing the +input. +*/ +template +struct json_sax +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + + /*! + @brief a null value was read + @return whether parsing should proceed + */ + virtual bool null() = 0; + + /*! + @brief a boolean value was read + @param[in] val boolean value + @return whether parsing should proceed + */ + virtual bool boolean(bool val) = 0; + + /*! + @brief an integer number was read + @param[in] val integer value + @return whether parsing should proceed + */ + virtual bool number_integer(number_integer_t val) = 0; + + /*! + @brief an unsigned integer number was read + @param[in] val unsigned integer value + @return whether parsing should proceed + */ + virtual bool number_unsigned(number_unsigned_t val) = 0; + + /*! + @brief an floating-point number was read + @param[in] val floating-point value + @param[in] s raw token value + @return whether parsing should proceed + */ + virtual bool number_float(number_float_t val, const string_t& s) = 0; + + /*! + @brief a string was read + @param[in] val string value + @return whether parsing should proceed + @note It is safe to move the passed string. + */ + virtual bool string(string_t& val) = 0; + + /*! + @brief a binary string was read + @param[in] val binary value + @return whether parsing should proceed + @note It is safe to move the passed binary. + */ + virtual bool binary(binary_t& val) = 0; + + /*! + @brief the beginning of an object was read + @param[in] elements number of object elements or -1 if unknown + @return whether parsing should proceed + @note binary formats may report the number of elements + */ + virtual bool start_object(std::size_t elements) = 0; + + /*! + @brief an object key was read + @param[in] val object key + @return whether parsing should proceed + @note It is safe to move the passed string. + */ + virtual bool key(string_t& val) = 0; + + /*! + @brief the end of an object was read + @return whether parsing should proceed + */ + virtual bool end_object() = 0; + + /*! + @brief the beginning of an array was read + @param[in] elements number of array elements or -1 if unknown + @return whether parsing should proceed + @note binary formats may report the number of elements + */ + virtual bool start_array(std::size_t elements) = 0; + + /*! + @brief the end of an array was read + @return whether parsing should proceed + */ + virtual bool end_array() = 0; + + /*! + @brief a parse error occurred + @param[in] position the position in the input where the error occurs + @param[in] last_token the last read token + @param[in] ex an exception object describing the error + @return whether parsing should proceed (must return false) + */ + virtual bool parse_error(std::size_t position, + const std::string& last_token, + const detail::exception& ex) = 0; + + virtual ~json_sax() = default; +}; + + +namespace detail +{ +/*! +@brief SAX implementation to create a JSON value from SAX events + +This class implements the @ref json_sax interface and processes the SAX events +to create a JSON value which makes it basically a DOM parser. The structure or +hierarchy of the JSON value is managed by the stack `ref_stack` which contains +a pointer to the respective array or object for each recursion depth. + +After successful parsing, the value that is passed by reference to the +constructor contains the parsed value. + +@tparam BasicJsonType the JSON type +*/ +template +class json_sax_dom_parser +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + + /*! + @param[in, out] r reference to a JSON value that is manipulated while + parsing + @param[in] allow_exceptions_ whether parse errors yield exceptions + */ + explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true) + : root(r), allow_exceptions(allow_exceptions_) + {} + + // make class move-only + json_sax_dom_parser(const json_sax_dom_parser&) = delete; + json_sax_dom_parser(json_sax_dom_parser&&) = default; + json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete; + json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; + ~json_sax_dom_parser() = default; + + bool null() + { + handle_value(nullptr); + return true; + } + + bool boolean(bool val) + { + handle_value(val); + return true; + } + + bool number_integer(number_integer_t val) + { + handle_value(val); + return true; + } + + bool number_unsigned(number_unsigned_t val) + { + handle_value(val); + return true; + } + + bool number_float(number_float_t val, const string_t& /*unused*/) + { + handle_value(val); + return true; + } + + bool string(string_t& val) + { + handle_value(val); + return true; + } + + bool binary(binary_t& val) + { + handle_value(std::move(val)); + return true; + } + + bool start_object(std::size_t len) + { + ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); + + if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive object size: " + std::to_string(len))); + } + + return true; + } + + bool key(string_t& val) + { + // add null at given key and store the reference for later + object_element = &(ref_stack.back()->m_value.object->operator[](val)); + return true; + } + + bool end_object() + { + ref_stack.pop_back(); + return true; + } + + bool start_array(std::size_t len) + { + ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); + + if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive array size: " + std::to_string(len))); + } + + return true; + } + + bool end_array() + { + ref_stack.pop_back(); + return true; + } + + template + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + const Exception& ex) + { + errored = true; + static_cast(ex); + if (allow_exceptions) + { + JSON_THROW(ex); + } + return false; + } + + constexpr bool is_errored() const + { + return errored; + } + + private: + /*! + @invariant If the ref stack is empty, then the passed value will be the new + root. + @invariant If the ref stack contains a value, then it is an array or an + object to which we can add elements + */ + template + JSON_HEDLEY_RETURNS_NON_NULL + BasicJsonType* handle_value(Value&& v) + { + if (ref_stack.empty()) + { + root = BasicJsonType(std::forward(v)); + return &root; + } + + JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + + if (ref_stack.back()->is_array()) + { + ref_stack.back()->m_value.array->emplace_back(std::forward(v)); + return &(ref_stack.back()->m_value.array->back()); + } + + JSON_ASSERT(ref_stack.back()->is_object()); + JSON_ASSERT(object_element); + *object_element = BasicJsonType(std::forward(v)); + return object_element; + } + + /// the parsed JSON value + BasicJsonType& root; + /// stack to model hierarchy of values + std::vector ref_stack {}; + /// helper to hold the reference for the next object element + BasicJsonType* object_element = nullptr; + /// whether a syntax error occurred + bool errored = false; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; +}; + +template +class json_sax_dom_callback_parser +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using parser_callback_t = typename BasicJsonType::parser_callback_t; + using parse_event_t = typename BasicJsonType::parse_event_t; + + json_sax_dom_callback_parser(BasicJsonType& r, + const parser_callback_t cb, + const bool allow_exceptions_ = true) + : root(r), callback(cb), allow_exceptions(allow_exceptions_) + { + keep_stack.push_back(true); + } + + // make class move-only + json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete; + json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; + json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete; + json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; + ~json_sax_dom_callback_parser() = default; + + bool null() + { + handle_value(nullptr); + return true; + } + + bool boolean(bool val) + { + handle_value(val); + return true; + } + + bool number_integer(number_integer_t val) + { + handle_value(val); + return true; + } + + bool number_unsigned(number_unsigned_t val) + { + handle_value(val); + return true; + } + + bool number_float(number_float_t val, const string_t& /*unused*/) + { + handle_value(val); + return true; + } + + bool string(string_t& val) + { + handle_value(val); + return true; + } + + bool binary(binary_t& val) + { + handle_value(std::move(val)); + return true; + } + + bool start_object(std::size_t len) + { + // check callback for object start + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); + keep_stack.push_back(keep); + + auto val = handle_value(BasicJsonType::value_t::object, true); + ref_stack.push_back(val.second); + + // check object limit + if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, "excessive object size: " + std::to_string(len))); + } + + return true; + } + + bool key(string_t& val) + { + BasicJsonType k = BasicJsonType(val); + + // check callback for key + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::key, k); + key_keep_stack.push_back(keep); + + // add discarded value at given key and store the reference for later + if (keep && ref_stack.back()) + { + object_element = &(ref_stack.back()->m_value.object->operator[](val) = discarded); + } + + return true; + } + + bool end_object() + { + if (ref_stack.back() && !callback(static_cast(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) + { + // discard object + *ref_stack.back() = discarded; + } + + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); + + if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) + { + // remove discarded value + for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) + { + if (it->is_discarded()) + { + ref_stack.back()->erase(it); + break; + } + } + } + + return true; + } + + bool start_array(std::size_t len) + { + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); + keep_stack.push_back(keep); + + auto val = handle_value(BasicJsonType::value_t::array, true); + ref_stack.push_back(val.second); + + // check array limit + if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, "excessive array size: " + std::to_string(len))); + } + + return true; + } + + bool end_array() + { + bool keep = true; + + if (ref_stack.back()) + { + keep = callback(static_cast(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); + if (!keep) + { + // discard array + *ref_stack.back() = discarded; + } + } + + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); + + // remove discarded value + if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) + { + ref_stack.back()->m_value.array->pop_back(); + } + + return true; + } + + template + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + const Exception& ex) + { + errored = true; + static_cast(ex); + if (allow_exceptions) + { + JSON_THROW(ex); + } + return false; + } + + constexpr bool is_errored() const + { + return errored; + } + + private: + /*! + @param[in] v value to add to the JSON value we build during parsing + @param[in] skip_callback whether we should skip calling the callback + function; this is required after start_array() and + start_object() SAX events, because otherwise we would call the + callback function with an empty array or object, respectively. + + @invariant If the ref stack is empty, then the passed value will be the new + root. + @invariant If the ref stack contains a value, then it is an array or an + object to which we can add elements + + @return pair of boolean (whether value should be kept) and pointer (to the + passed value in the ref_stack hierarchy; nullptr if not kept) + */ + template + std::pair handle_value(Value&& v, const bool skip_callback = false) + { + JSON_ASSERT(!keep_stack.empty()); + + // do not handle this value if we know it would be added to a discarded + // container + if (!keep_stack.back()) + { + return {false, nullptr}; + } + + // create value + auto value = BasicJsonType(std::forward(v)); + + // check callback + const bool keep = skip_callback || callback(static_cast(ref_stack.size()), parse_event_t::value, value); + + // do not handle this value if we just learnt it shall be discarded + if (!keep) + { + return {false, nullptr}; + } + + if (ref_stack.empty()) + { + root = std::move(value); + return {true, &root}; + } + + // skip this value if we already decided to skip the parent + // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360) + if (!ref_stack.back()) + { + return {false, nullptr}; + } + + // we now only expect arrays and objects + JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + + // array + if (ref_stack.back()->is_array()) + { + ref_stack.back()->m_value.array->push_back(std::move(value)); + return {true, &(ref_stack.back()->m_value.array->back())}; + } + + // object + JSON_ASSERT(ref_stack.back()->is_object()); + // check if we should store an element for the current key + JSON_ASSERT(!key_keep_stack.empty()); + const bool store_element = key_keep_stack.back(); + key_keep_stack.pop_back(); + + if (!store_element) + { + return {false, nullptr}; + } + + JSON_ASSERT(object_element); + *object_element = std::move(value); + return {true, object_element}; + } + + /// the parsed JSON value + BasicJsonType& root; + /// stack to model hierarchy of values + std::vector ref_stack {}; + /// stack to manage which values to keep + std::vector keep_stack {}; + /// stack to manage which object keys to keep + std::vector key_keep_stack {}; + /// helper to hold the reference for the next object element + BasicJsonType* object_element = nullptr; + /// whether a syntax error occurred + bool errored = false; + /// callback function + const parser_callback_t callback = nullptr; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; + /// a discarded value for the callback + BasicJsonType discarded = BasicJsonType::value_t::discarded; +}; + +template +class json_sax_acceptor +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + + bool null() + { + return true; + } + + bool boolean(bool /*unused*/) + { + return true; + } + + bool number_integer(number_integer_t /*unused*/) + { + return true; + } + + bool number_unsigned(number_unsigned_t /*unused*/) + { + return true; + } + + bool number_float(number_float_t /*unused*/, const string_t& /*unused*/) + { + return true; + } + + bool string(string_t& /*unused*/) + { + return true; + } + + bool binary(binary_t& /*unused*/) + { + return true; + } + + bool start_object(std::size_t /*unused*/ = std::size_t(-1)) + { + return true; + } + + bool key(string_t& /*unused*/) + { + return true; + } + + bool end_object() + { + return true; + } + + bool start_array(std::size_t /*unused*/ = std::size_t(-1)) + { + return true; + } + + bool end_array() + { + return true; + } + + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/) + { + return false; + } +}; +} // namespace detail + +} // namespace nlohmann + +// #include + + +#include // array +#include // localeconv +#include // size_t +#include // snprintf +#include // strtof, strtod, strtold, strtoll, strtoull +#include // initializer_list +#include // char_traits, string +#include // move +#include // vector + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////// +// lexer // +/////////// + +template +class lexer_base +{ + public: + /// token types for the parser + enum class token_type + { + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal + value_string, ///< a string -- use get_string() for actual value + value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value + value_integer, ///< a signed integer -- use get_number_integer() for actual value + value_float, ///< an floating point number -- use get_number_float() for actual value + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` + parse_error, ///< indicating a parse error + end_of_input, ///< indicating the end of the input buffer + literal_or_value ///< a literal or the begin of a value (only for diagnostics) + }; + + /// return name of values of type token_type (only used for errors) + JSON_HEDLEY_RETURNS_NON_NULL + JSON_HEDLEY_CONST + static const char* token_type_name(const token_type t) noexcept + { + switch (t) + { + case token_type::uninitialized: + return ""; + case token_type::literal_true: + return "true literal"; + case token_type::literal_false: + return "false literal"; + case token_type::literal_null: + return "null literal"; + case token_type::value_string: + return "string literal"; + case token_type::value_unsigned: + case token_type::value_integer: + case token_type::value_float: + return "number literal"; + case token_type::begin_array: + return "'['"; + case token_type::begin_object: + return "'{'"; + case token_type::end_array: + return "']'"; + case token_type::end_object: + return "'}'"; + case token_type::name_separator: + return "':'"; + case token_type::value_separator: + return "','"; + case token_type::parse_error: + return ""; + case token_type::end_of_input: + return "end of input"; + case token_type::literal_or_value: + return "'[', '{', or a literal"; + // LCOV_EXCL_START + default: // catch non-enum values + return "unknown token"; + // LCOV_EXCL_STOP + } + } +}; +/*! +@brief lexical analysis + +This class organizes the lexical analysis during JSON deserialization. +*/ +template +class lexer : public lexer_base +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using char_type = typename InputAdapterType::char_type; + using char_int_type = typename std::char_traits::int_type; + + public: + using token_type = typename lexer_base::token_type; + + explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) + : ia(std::move(adapter)) + , ignore_comments(ignore_comments_) + , decimal_point_char(static_cast(get_decimal_point())) + {} + + // delete because of pointer members + lexer(const lexer&) = delete; + lexer(lexer&&) = default; + lexer& operator=(lexer&) = delete; + lexer& operator=(lexer&&) = default; + ~lexer() = default; + + private: + ///////////////////// + // locales + ///////////////////// + + /// return the locale-dependent decimal point + JSON_HEDLEY_PURE + static char get_decimal_point() noexcept + { + const auto* loc = localeconv(); + JSON_ASSERT(loc != nullptr); + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); + } + + ///////////////////// + // scan functions + ///////////////////// + + /*! + @brief get codepoint from 4 hex characters following `\u` + + For input "\u c1 c2 c3 c4" the codepoint is: + (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 + = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) + + Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' + must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The + conversion is done by subtracting the offset (0x30, 0x37, and 0x57) + between the ASCII value of the character and the desired integer value. + + @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or + non-hex character) + */ + int get_codepoint() + { + // this function only makes sense after reading `\u` + JSON_ASSERT(current == 'u'); + int codepoint = 0; + + const auto factors = { 12u, 8u, 4u, 0u }; + for (const auto factor : factors) + { + get(); + + if (current >= '0' && current <= '9') + { + codepoint += static_cast((static_cast(current) - 0x30u) << factor); + } + else if (current >= 'A' && current <= 'F') + { + codepoint += static_cast((static_cast(current) - 0x37u) << factor); + } + else if (current >= 'a' && current <= 'f') + { + codepoint += static_cast((static_cast(current) - 0x57u) << factor); + } + else + { + return -1; + } + } + + JSON_ASSERT(0x0000 <= codepoint && codepoint <= 0xFFFF); + return codepoint; + } + + /*! + @brief check if the next byte(s) are inside a given range + + Adds the current byte and, for each passed range, reads a new byte and + checks if it is inside the range. If a violation was detected, set up an + error message and return false. Otherwise, return true. + + @param[in] ranges list of integers; interpreted as list of pairs of + inclusive lower and upper bound, respectively + + @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, + 1, 2, or 3 pairs. This precondition is enforced by an assertion. + + @return true if and only if no range violation was detected + */ + bool next_byte_in_range(std::initializer_list ranges) + { + JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6); + add(current); + + for (auto range = ranges.begin(); range != ranges.end(); ++range) + { + get(); + if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) + { + add(current); + } + else + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return false; + } + } + + return true; + } + + /*! + @brief scan a string literal + + This function scans a string according to Sect. 7 of RFC 7159. While + scanning, bytes are escaped and copied into buffer token_buffer. Then the + function returns successfully, token_buffer is *not* null-terminated (as it + may contain \0 bytes), and token_buffer.size() is the number of bytes in the + string. + + @return token_type::value_string if string could be successfully scanned, + token_type::parse_error otherwise + + @note In case of errors, variable error_message contains a textual + description. + */ + token_type scan_string() + { + // reset token_buffer (ignore opening quote) + reset(); + + // we entered the function by reading an open quote + JSON_ASSERT(current == '\"'); + + while (true) + { + // get next character + switch (get()) + { + // end of file while parsing string + case std::char_traits::eof(): + { + error_message = "invalid string: missing closing quote"; + return token_type::parse_error; + } + + // closing quote + case '\"': + { + return token_type::value_string; + } + + // escapes + case '\\': + { + switch (get()) + { + // quotation mark + case '\"': + add('\"'); + break; + // reverse solidus + case '\\': + add('\\'); + break; + // solidus + case '/': + add('/'); + break; + // backspace + case 'b': + add('\b'); + break; + // form feed + case 'f': + add('\f'); + break; + // line feed + case 'n': + add('\n'); + break; + // carriage return + case 'r': + add('\r'); + break; + // tab + case 't': + add('\t'); + break; + + // unicode escapes + case 'u': + { + const int codepoint1 = get_codepoint(); + int codepoint = codepoint1; // start with codepoint1 + + if (JSON_HEDLEY_UNLIKELY(codepoint1 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if code point is a high surrogate + if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF) + { + // expect next \uxxxx entry + if (JSON_HEDLEY_LIKELY(get() == '\\' && get() == 'u')) + { + const int codepoint2 = get_codepoint(); + + if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if codepoint2 is a low surrogate + if (JSON_HEDLEY_LIKELY(0xDC00 <= codepoint2 && codepoint2 <= 0xDFFF)) + { + // overwrite codepoint + codepoint = static_cast( + // high surrogate occupies the most significant 22 bits + (static_cast(codepoint1) << 10u) + // low surrogate occupies the least significant 15 bits + + static_cast(codepoint2) + // there is still the 0xD800, 0xDC00 and 0x10000 noise + // in the result so we have to subtract with: + // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 + - 0x35FDC00u); + } + else + { + error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF)) + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; + return token_type::parse_error; + } + } + + // result of the above calculation yields a proper codepoint + JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF); + + // translate codepoint into bytes + if (codepoint < 0x80) + { + // 1-byte characters: 0xxxxxxx (ASCII) + add(static_cast(codepoint)); + } + else if (codepoint <= 0x7FF) + { + // 2-byte characters: 110xxxxx 10xxxxxx + add(static_cast(0xC0u | (static_cast(codepoint) >> 6u))); + add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); + } + else if (codepoint <= 0xFFFF) + { + // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx + add(static_cast(0xE0u | (static_cast(codepoint) >> 12u))); + add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); + add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); + } + else + { + // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + add(static_cast(0xF0u | (static_cast(codepoint) >> 18u))); + add(static_cast(0x80u | ((static_cast(codepoint) >> 12u) & 0x3Fu))); + add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); + add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); + } + + break; + } + + // other characters after escape + default: + error_message = "invalid string: forbidden character after backslash"; + return token_type::parse_error; + } + + break; + } + + // invalid control characters + case 0x00: + { + error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000"; + return token_type::parse_error; + } + + case 0x01: + { + error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001"; + return token_type::parse_error; + } + + case 0x02: + { + error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002"; + return token_type::parse_error; + } + + case 0x03: + { + error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003"; + return token_type::parse_error; + } + + case 0x04: + { + error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004"; + return token_type::parse_error; + } + + case 0x05: + { + error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005"; + return token_type::parse_error; + } + + case 0x06: + { + error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006"; + return token_type::parse_error; + } + + case 0x07: + { + error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007"; + return token_type::parse_error; + } + + case 0x08: + { + error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b"; + return token_type::parse_error; + } + + case 0x09: + { + error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t"; + return token_type::parse_error; + } + + case 0x0A: + { + error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n"; + return token_type::parse_error; + } + + case 0x0B: + { + error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B"; + return token_type::parse_error; + } + + case 0x0C: + { + error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f"; + return token_type::parse_error; + } + + case 0x0D: + { + error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r"; + return token_type::parse_error; + } + + case 0x0E: + { + error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E"; + return token_type::parse_error; + } + + case 0x0F: + { + error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F"; + return token_type::parse_error; + } + + case 0x10: + { + error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010"; + return token_type::parse_error; + } + + case 0x11: + { + error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011"; + return token_type::parse_error; + } + + case 0x12: + { + error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012"; + return token_type::parse_error; + } + + case 0x13: + { + error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013"; + return token_type::parse_error; + } + + case 0x14: + { + error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014"; + return token_type::parse_error; + } + + case 0x15: + { + error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015"; + return token_type::parse_error; + } + + case 0x16: + { + error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016"; + return token_type::parse_error; + } + + case 0x17: + { + error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017"; + return token_type::parse_error; + } + + case 0x18: + { + error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018"; + return token_type::parse_error; + } + + case 0x19: + { + error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019"; + return token_type::parse_error; + } + + case 0x1A: + { + error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A"; + return token_type::parse_error; + } + + case 0x1B: + { + error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B"; + return token_type::parse_error; + } + + case 0x1C: + { + error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C"; + return token_type::parse_error; + } + + case 0x1D: + { + error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D"; + return token_type::parse_error; + } + + case 0x1E: + { + error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E"; + return token_type::parse_error; + } + + case 0x1F: + { + error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F"; + return token_type::parse_error; + } + + // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) + case 0x20: + case 0x21: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + { + add(current); + break; + } + + // U+0080..U+07FF: bytes C2..DF 80..BF + case 0xC2: + case 0xC3: + case 0xC4: + case 0xC5: + case 0xC6: + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD5: + case 0xD6: + case 0xD7: + case 0xD8: + case 0xD9: + case 0xDA: + case 0xDB: + case 0xDC: + case 0xDD: + case 0xDE: + case 0xDF: + { + if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF}))) + { + return token_type::parse_error; + } + break; + } + + // U+0800..U+0FFF: bytes E0 A0..BF 80..BF + case 0xE0: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF + // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xEE: + case 0xEF: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+D000..U+D7FF: bytes ED 80..9F 80..BF + case 0xED: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF + case 0xF0: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF + case 0xF1: + case 0xF2: + case 0xF3: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF + case 0xF4: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // remaining bytes (80..C1 and F5..FF) are ill-formed + default: + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return token_type::parse_error; + } + } + } + } + + /*! + * @brief scan a comment + * @return whether comment could be scanned successfully + */ + bool scan_comment() + { + switch (get()) + { + // single-line comments skip input until a newline or EOF is read + case '/': + { + while (true) + { + switch (get()) + { + case '\n': + case '\r': + case std::char_traits::eof(): + case '\0': + return true; + + default: + break; + } + } + } + + // multi-line comments skip input until */ is read + case '*': + { + while (true) + { + switch (get()) + { + case std::char_traits::eof(): + case '\0': + { + error_message = "invalid comment; missing closing '*/'"; + return false; + } + + case '*': + { + switch (get()) + { + case '/': + return true; + + default: + { + unget(); + continue; + } + } + } + + default: + continue; + } + } + } + + // unexpected character after reading '/' + default: + { + error_message = "invalid comment; expecting '/' or '*' after '/'"; + return false; + } + } + } + + JSON_HEDLEY_NON_NULL(2) + static void strtof(float& f, const char* str, char** endptr) noexcept + { + f = std::strtof(str, endptr); + } + + JSON_HEDLEY_NON_NULL(2) + static void strtof(double& f, const char* str, char** endptr) noexcept + { + f = std::strtod(str, endptr); + } + + JSON_HEDLEY_NON_NULL(2) + static void strtof(long double& f, const char* str, char** endptr) noexcept + { + f = std::strtold(str, endptr); + } + + /*! + @brief scan a number literal + + This function scans a string according to Sect. 6 of RFC 7159. + + The function is realized with a deterministic finite state machine derived + from the grammar described in RFC 7159. Starting in state "init", the + input is read and used to determined the next state. Only state "done" + accepts the number. State "error" is a trap state to model errors. In the + table below, "anything" means any character but the ones listed before. + + state | 0 | 1-9 | e E | + | - | . | anything + ---------|----------|----------|----------|---------|---------|----------|----------- + init | zero | any1 | [error] | [error] | minus | [error] | [error] + minus | zero | any1 | [error] | [error] | [error] | [error] | [error] + zero | done | done | exponent | done | done | decimal1 | done + any1 | any1 | any1 | exponent | done | done | decimal1 | done + decimal1 | decimal2 | decimal2 | [error] | [error] | [error] | [error] | [error] + decimal2 | decimal2 | decimal2 | exponent | done | done | done | done + exponent | any2 | any2 | [error] | sign | sign | [error] | [error] + sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] + any2 | any2 | any2 | done | done | done | done | done + + The state machine is realized with one label per state (prefixed with + "scan_number_") and `goto` statements between them. The state machine + contains cycles, but any cycle can be left when EOF is read. Therefore, + the function is guaranteed to terminate. + + During scanning, the read bytes are stored in token_buffer. This string is + then converted to a signed integer, an unsigned integer, or a + floating-point number. + + @return token_type::value_unsigned, token_type::value_integer, or + token_type::value_float if number could be successfully scanned, + token_type::parse_error otherwise + + @note The scanner is independent of the current locale. Internally, the + locale's decimal point is used instead of `.` to work with the + locale-dependent converters. + */ + token_type scan_number() // lgtm [cpp/use-of-goto] + { + // reset token_buffer to store the number's bytes + reset(); + + // the type of the parsed number; initially set to unsigned; will be + // changed if minus sign, decimal point or exponent is read + token_type number_type = token_type::value_unsigned; + + // state (init): we just found out we need to scan a number + switch (current) + { + case '-': + { + add(current); + goto scan_number_minus; + } + + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + // all other characters are rejected outside scan_number() + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + +scan_number_minus: + // state: we just parsed a leading minus sign + number_type = token_type::value_integer; + switch (get()) + { + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + default: + { + error_message = "invalid number; expected digit after '-'"; + return token_type::parse_error; + } + } + +scan_number_zero: + // state: we just parse a zero (maybe with a leading minus sign) + switch (get()) + { + case '.': + { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_any1: + // state: we just parsed a number 0-9 (maybe with a leading minus sign) + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + case '.': + { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_decimal1: + // state: we just parsed a decimal point + number_type = token_type::value_float; + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + + default: + { + error_message = "invalid number; expected digit after '.'"; + return token_type::parse_error; + } + } + +scan_number_decimal2: + // we just parsed at least one number after a decimal point + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_exponent: + // we just parsed an exponent + number_type = token_type::value_float; + switch (get()) + { + case '+': + case '-': + { + add(current); + goto scan_number_sign; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + { + error_message = + "invalid number; expected '+', '-', or digit after exponent"; + return token_type::parse_error; + } + } + +scan_number_sign: + // we just parsed an exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + { + error_message = "invalid number; expected digit after exponent sign"; + return token_type::parse_error; + } + } + +scan_number_any2: + // we just parsed a number after the exponent or exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + goto scan_number_done; + } + +scan_number_done: + // unget the character after the number (we only read it to know that + // we are done scanning a number) + unget(); + + char* endptr = nullptr; + errno = 0; + + // try to parse integers first and fall back to floats + if (number_type == token_type::value_unsigned) + { + const auto x = std::strtoull(token_buffer.data(), &endptr, 10); + + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + + if (errno == 0) + { + value_unsigned = static_cast(x); + if (value_unsigned == x) + { + return token_type::value_unsigned; + } + } + } + else if (number_type == token_type::value_integer) + { + const auto x = std::strtoll(token_buffer.data(), &endptr, 10); + + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + + if (errno == 0) + { + value_integer = static_cast(x); + if (value_integer == x) + { + return token_type::value_integer; + } + } + } + + // this code is reached if we parse a floating-point number or if an + // integer conversion above failed + strtof(value_float, token_buffer.data(), &endptr); + + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + + return token_type::value_float; + } + + /*! + @param[in] literal_text the literal text to expect + @param[in] length the length of the passed literal text + @param[in] return_type the token type to return on success + */ + JSON_HEDLEY_NON_NULL(2) + token_type scan_literal(const char_type* literal_text, const std::size_t length, + token_type return_type) + { + JSON_ASSERT(std::char_traits::to_char_type(current) == literal_text[0]); + for (std::size_t i = 1; i < length; ++i) + { + if (JSON_HEDLEY_UNLIKELY(std::char_traits::to_char_type(get()) != literal_text[i])) + { + error_message = "invalid literal"; + return token_type::parse_error; + } + } + return return_type; + } + + ///////////////////// + // input management + ///////////////////// + + /// reset token_buffer; current character is beginning of token + void reset() noexcept + { + token_buffer.clear(); + token_string.clear(); + token_string.push_back(std::char_traits::to_char_type(current)); + } + + /* + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a + `std::char_traits::eof()` in that case. Stores the scanned characters + for use in error messages. + + @return character read from the input + */ + char_int_type get() + { + ++position.chars_read_total; + ++position.chars_read_current_line; + + if (next_unget) + { + // just reset the next_unget variable and work with current + next_unget = false; + } + else + { + current = ia.get_character(); + } + + if (JSON_HEDLEY_LIKELY(current != std::char_traits::eof())) + { + token_string.push_back(std::char_traits::to_char_type(current)); + } + + if (current == '\n') + { + ++position.lines_read; + position.chars_read_current_line = 0; + } + + return current; + } + + /*! + @brief unget current character (read it again on next get) + + We implement unget by setting variable next_unget to true. The input is not + changed - we just simulate ungetting by modifying chars_read_total, + chars_read_current_line, and token_string. The next call to get() will + behave as if the unget character is read again. + */ + void unget() + { + next_unget = true; + + --position.chars_read_total; + + // in case we "unget" a newline, we have to also decrement the lines_read + if (position.chars_read_current_line == 0) + { + if (position.lines_read > 0) + { + --position.lines_read; + } + } + else + { + --position.chars_read_current_line; + } + + if (JSON_HEDLEY_LIKELY(current != std::char_traits::eof())) + { + JSON_ASSERT(!token_string.empty()); + token_string.pop_back(); + } + } + + /// add a character to token_buffer + void add(char_int_type c) + { + token_buffer.push_back(static_cast(c)); + } + + public: + ///////////////////// + // value getters + ///////////////////// + + /// return integer value + constexpr number_integer_t get_number_integer() const noexcept + { + return value_integer; + } + + /// return unsigned integer value + constexpr number_unsigned_t get_number_unsigned() const noexcept + { + return value_unsigned; + } + + /// return floating-point value + constexpr number_float_t get_number_float() const noexcept + { + return value_float; + } + + /// return current string value (implicitly resets the token; useful only once) + string_t& get_string() + { + return token_buffer; + } + + ///////////////////// + // diagnostics + ///////////////////// + + /// return position of last read token + constexpr position_t get_position() const noexcept + { + return position; + } + + /// return the last read token (for errors only). Will never contain EOF + /// (an arbitrary value that is not a valid char value, often -1), because + /// 255 may legitimately occur. May contain NUL, which should be escaped. + std::string get_token_string() const + { + // escape control characters + std::string result; + for (const auto c : token_string) + { + if (static_cast(c) <= '\x1F') + { + // escape control characters + std::array cs{{}}; + (std::snprintf)(cs.data(), cs.size(), "", static_cast(c)); + result += cs.data(); + } + else + { + // add character as is + result.push_back(static_cast(c)); + } + } + + return result; + } + + /// return syntax error message + JSON_HEDLEY_RETURNS_NON_NULL + constexpr const char* get_error_message() const noexcept + { + return error_message; + } + + ///////////////////// + // actual scanner + ///////////////////// + + /*! + @brief skip the UTF-8 byte order mark + @return true iff there is no BOM or the correct BOM has been skipped + */ + bool skip_bom() + { + if (get() == 0xEF) + { + // check if we completely parse the BOM + return get() == 0xBB && get() == 0xBF; + } + + // the first character is not the beginning of the BOM; unget it to + // process is later + unget(); + return true; + } + + void skip_whitespace() + { + do + { + get(); + } + while (current == ' ' || current == '\t' || current == '\n' || current == '\r'); + } + + token_type scan() + { + // initially, skip the BOM + if (position.chars_read_total == 0 && !skip_bom()) + { + error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given"; + return token_type::parse_error; + } + + // read next character and ignore whitespace + skip_whitespace(); + + // ignore comments + while (ignore_comments && current == '/') + { + if (!scan_comment()) + { + return token_type::parse_error; + } + + // skip following whitespace + skip_whitespace(); + } + + switch (current) + { + // structural characters + case '[': + return token_type::begin_array; + case ']': + return token_type::end_array; + case '{': + return token_type::begin_object; + case '}': + return token_type::end_object; + case ':': + return token_type::name_separator; + case ',': + return token_type::value_separator; + + // literals + case 't': + { + std::array true_literal = {{'t', 'r', 'u', 'e'}}; + return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true); + } + case 'f': + { + std::array false_literal = {{'f', 'a', 'l', 's', 'e'}}; + return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false); + } + case 'n': + { + std::array null_literal = {{'n', 'u', 'l', 'l'}}; + return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null); + } + + // string + case '\"': + return scan_string(); + + // number + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return scan_number(); + + // end of input (the null byte is needed when parsing from + // string literals) + case '\0': + case std::char_traits::eof(): + return token_type::end_of_input; + + // error + default: + error_message = "invalid literal"; + return token_type::parse_error; + } + } + + private: + /// input adapter + InputAdapterType ia; + + /// whether comments should be ignored (true) or signaled as errors (false) + const bool ignore_comments = false; + + /// the current character + char_int_type current = std::char_traits::eof(); + + /// whether the next get() call should just return current + bool next_unget = false; + + /// the start position of the current token + position_t position {}; + + /// raw input token string (for error messages) + std::vector token_string {}; + + /// buffer for variable-length tokens (numbers, strings) + string_t token_buffer {}; + + /// a description of occurred lexer errors + const char* error_message = ""; + + // number values + number_integer_t value_integer = 0; + number_unsigned_t value_unsigned = 0; + number_float_t value_float = 0; + + /// the decimal point + const char_int_type decimal_point_char = '.'; +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + + +#include // size_t +#include // declval +#include // string + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template +using null_function_t = decltype(std::declval().null()); + +template +using boolean_function_t = + decltype(std::declval().boolean(std::declval())); + +template +using number_integer_function_t = + decltype(std::declval().number_integer(std::declval())); + +template +using number_unsigned_function_t = + decltype(std::declval().number_unsigned(std::declval())); + +template +using number_float_function_t = decltype(std::declval().number_float( + std::declval(), std::declval())); + +template +using string_function_t = + decltype(std::declval().string(std::declval())); + +template +using binary_function_t = + decltype(std::declval().binary(std::declval())); + +template +using start_object_function_t = + decltype(std::declval().start_object(std::declval())); + +template +using key_function_t = + decltype(std::declval().key(std::declval())); + +template +using end_object_function_t = decltype(std::declval().end_object()); + +template +using start_array_function_t = + decltype(std::declval().start_array(std::declval())); + +template +using end_array_function_t = decltype(std::declval().end_array()); + +template +using parse_error_function_t = decltype(std::declval().parse_error( + std::declval(), std::declval(), + std::declval())); + +template +struct is_sax +{ + private: + static_assert(is_basic_json::value, + "BasicJsonType must be of type basic_json<...>"); + + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using exception_t = typename BasicJsonType::exception; + + public: + static constexpr bool value = + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value; +}; + +template +struct is_sax_static_asserts +{ + private: + static_assert(is_basic_json::value, + "BasicJsonType must be of type basic_json<...>"); + + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using exception_t = typename BasicJsonType::exception; + + public: + static_assert(is_detected_exact::value, + "Missing/invalid function: bool null()"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool boolean(bool)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool boolean(bool)"); + static_assert( + is_detected_exact::value, + "Missing/invalid function: bool number_integer(number_integer_t)"); + static_assert( + is_detected_exact::value, + "Missing/invalid function: bool number_unsigned(number_unsigned_t)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool number_float(number_float_t, const string_t&)"); + static_assert( + is_detected_exact::value, + "Missing/invalid function: bool string(string_t&)"); + static_assert( + is_detected_exact::value, + "Missing/invalid function: bool binary(binary_t&)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool start_object(std::size_t)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool key(string_t&)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool end_object()"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool start_array(std::size_t)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool end_array()"); + static_assert( + is_detected_exact::value, + "Missing/invalid function: bool parse_error(std::size_t, const " + "std::string&, const exception&)"); +}; +} // namespace detail +} // namespace nlohmann + +// #include + + +namespace nlohmann +{ +namespace detail +{ + +/// how to treat CBOR tags +enum class cbor_tag_handler_t +{ + error, ///< throw a parse_error exception in case of a tag + ignore ///< ignore tags +}; + +/*! +@brief determine system byte order + +@return true if and only if system's byte order is little endian + +@note from https://stackoverflow.com/a/1001328/266378 +*/ +static inline bool little_endianess(int num = 1) noexcept +{ + return *reinterpret_cast(&num) == 1; +} + + +/////////////////// +// binary reader // +/////////////////// + +/*! +@brief deserialization of CBOR, MessagePack, and UBJSON values +*/ +template> +class binary_reader +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using json_sax_t = SAX; + using char_type = typename InputAdapterType::char_type; + using char_int_type = typename std::char_traits::int_type; + + public: + /*! + @brief create a binary reader + + @param[in] adapter input adapter to read from + */ + explicit binary_reader(InputAdapterType&& adapter) : ia(std::move(adapter)) + { + (void)detail::is_sax_static_asserts {}; + } + + // make class move-only + binary_reader(const binary_reader&) = delete; + binary_reader(binary_reader&&) = default; + binary_reader& operator=(const binary_reader&) = delete; + binary_reader& operator=(binary_reader&&) = default; + ~binary_reader() = default; + + /*! + @param[in] format the binary format to parse + @param[in] sax_ a SAX event processor + @param[in] strict whether to expect the input to be consumed completed + @param[in] tag_handler how to treat CBOR tags + + @return + */ + JSON_HEDLEY_NON_NULL(3) + bool sax_parse(const input_format_t format, + json_sax_t* sax_, + const bool strict = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + sax = sax_; + bool result = false; + + switch (format) + { + case input_format_t::bson: + result = parse_bson_internal(); + break; + + case input_format_t::cbor: + result = parse_cbor_internal(true, tag_handler); + break; + + case input_format_t::msgpack: + result = parse_msgpack_internal(); + break; + + case input_format_t::ubjson: + result = parse_ubjson_internal(); + break; + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + + // strict mode: next byte must be EOF + if (result && strict) + { + if (format == input_format_t::ubjson) + { + get_ignore_noop(); + } + else + { + get(); + } + + if (JSON_HEDLEY_UNLIKELY(current != std::char_traits::eof())) + { + return sax->parse_error(chars_read, get_token_string(), + parse_error::create(110, chars_read, exception_message(format, "expected end of input; last byte: 0x" + get_token_string(), "value"))); + } + } + + return result; + } + + private: + ////////// + // BSON // + ////////// + + /*! + @brief Reads in a BSON-object and passes it to the SAX-parser. + @return whether a valid BSON-value was passed to the SAX parser + */ + bool parse_bson_internal() + { + std::int32_t document_size{}; + get_number(input_format_t::bson, document_size); + + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/false))) + { + return false; + } + + return sax->end_object(); + } + + /*! + @brief Parses a C-style string from the BSON input. + @param[in, out] result A reference to the string variable where the read + string is to be stored. + @return `true` if the \x00-byte indicating the end of the string was + encountered before the EOF; false` indicates an unexpected EOF. + */ + bool get_bson_cstr(string_t& result) + { + auto out = std::back_inserter(result); + while (true) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, "cstring"))) + { + return false; + } + if (current == 0x00) + { + return true; + } + *out++ = static_cast(current); + } + } + + /*! + @brief Parses a zero-terminated string of length @a len from the BSON + input. + @param[in] len The length (including the zero-byte at the end) of the + string to be read. + @param[in, out] result A reference to the string variable where the read + string is to be stored. + @tparam NumberType The type of the length @a len + @pre len >= 1 + @return `true` if the string was successfully parsed + */ + template + bool get_bson_string(const NumberType len, string_t& result) + { + if (JSON_HEDLEY_UNLIKELY(len < 1)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "string length must be at least 1, is " + std::to_string(len), "string"))); + } + + return get_string(input_format_t::bson, len - static_cast(1), result) && get() != std::char_traits::eof(); + } + + /*! + @brief Parses a byte array input of length @a len from the BSON input. + @param[in] len The length of the byte array to be read. + @param[in, out] result A reference to the binary variable where the read + array is to be stored. + @tparam NumberType The type of the length @a len + @pre len >= 0 + @return `true` if the byte array was successfully parsed + */ + template + bool get_bson_binary(const NumberType len, binary_t& result) + { + if (JSON_HEDLEY_UNLIKELY(len < 0)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "byte array length cannot be negative, is " + std::to_string(len), "binary"))); + } + + // All BSON binary values have a subtype + std::uint8_t subtype{}; + get_number(input_format_t::bson, subtype); + result.set_subtype(subtype); + + return get_binary(input_format_t::bson, len, result); + } + + /*! + @brief Read a BSON document element of the given @a element_type. + @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html + @param[in] element_type_parse_position The position in the input stream, + where the `element_type` was read. + @warning Not all BSON element types are supported yet. An unsupported + @a element_type will give rise to a parse_error.114: + Unsupported BSON record type 0x... + @return whether a valid BSON-object/array was passed to the SAX parser + */ + bool parse_bson_element_internal(const char_int_type element_type, + const std::size_t element_type_parse_position) + { + switch (element_type) + { + case 0x01: // double + { + double number{}; + return get_number(input_format_t::bson, number) && sax->number_float(static_cast(number), ""); + } + + case 0x02: // string + { + std::int32_t len{}; + string_t value; + return get_number(input_format_t::bson, len) && get_bson_string(len, value) && sax->string(value); + } + + case 0x03: // object + { + return parse_bson_internal(); + } + + case 0x04: // array + { + return parse_bson_array(); + } + + case 0x05: // binary + { + std::int32_t len{}; + binary_t value; + return get_number(input_format_t::bson, len) && get_bson_binary(len, value) && sax->binary(value); + } + + case 0x08: // boolean + { + return sax->boolean(get() != 0); + } + + case 0x0A: // null + { + return sax->null(); + } + + case 0x10: // int32 + { + std::int32_t value{}; + return get_number(input_format_t::bson, value) && sax->number_integer(value); + } + + case 0x12: // int64 + { + std::int64_t value{}; + return get_number(input_format_t::bson, value) && sax->number_integer(value); + } + + default: // anything else not supported (yet) + { + std::array cr{{}}; + (std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(element_type)); + return sax->parse_error(element_type_parse_position, std::string(cr.data()), parse_error::create(114, element_type_parse_position, "Unsupported BSON record type 0x" + std::string(cr.data()))); + } + } + } + + /*! + @brief Read a BSON element list (as specified in the BSON-spec) + + The same binary layout is used for objects and arrays, hence it must be + indicated with the argument @a is_array which one is expected + (true --> array, false --> object). + + @param[in] is_array Determines if the element list being read is to be + treated as an object (@a is_array == false), or as an + array (@a is_array == true). + @return whether a valid BSON-object/array was passed to the SAX parser + */ + bool parse_bson_element_list(const bool is_array) + { + string_t key; + + while (auto element_type = get()) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, "element list"))) + { + return false; + } + + const std::size_t element_type_parse_position = chars_read; + if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key))) + { + return false; + } + + if (!is_array && !sax->key(key)) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_internal(element_type, element_type_parse_position))) + { + return false; + } + + // get_bson_cstr only appends + key.clear(); + } + + return true; + } + + /*! + @brief Reads an array from the BSON input and passes it to the SAX-parser. + @return whether a valid BSON-array was passed to the SAX parser + */ + bool parse_bson_array() + { + std::int32_t document_size{}; + get_number(input_format_t::bson, document_size); + + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/true))) + { + return false; + } + + return sax->end_array(); + } + + ////////// + // CBOR // + ////////// + + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true) or whether the last read character should + be considered instead (false) + @param[in] tag_handler how CBOR tags should be treated + + @return whether a valid CBOR value was passed to the SAX parser + */ + bool parse_cbor_internal(const bool get_char, + const cbor_tag_handler_t tag_handler) + { + switch (get_char ? get() : current) + { + // EOF + case std::char_traits::eof(): + return unexpect_eof(input_format_t::cbor, "value"); + + // Integer 0x00..0x17 (0..23) + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + return sax->number_unsigned(static_cast(current)); + + case 0x18: // Unsigned integer (one-byte uint8_t follows) + { + std::uint8_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + case 0x19: // Unsigned integer (two-byte uint16_t follows) + { + std::uint16_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + case 0x1A: // Unsigned integer (four-byte uint32_t follows) + { + std::uint32_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + case 0x1B: // Unsigned integer (eight-byte uint64_t follows) + { + std::uint64_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + // Negative integer -1-0x00..-1-0x17 (-1..-24) + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + return sax->number_integer(static_cast(0x20 - 1 - current)); + + case 0x38: // Negative integer (one-byte uint8_t follows) + { + std::uint8_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); + } + + case 0x39: // Negative integer -1-n (two-byte uint16_t follows) + { + std::uint16_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); + } + + case 0x3A: // Negative integer -1-n (four-byte uint32_t follows) + { + std::uint32_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); + } + + case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows) + { + std::uint64_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) + - static_cast(number)); + } + + // Binary data (0x00..0x17 bytes follow) + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: // Binary data (one-byte uint8_t for n follows) + case 0x59: // Binary data (two-byte uint16_t for n follow) + case 0x5A: // Binary data (four-byte uint32_t for n follow) + case 0x5B: // Binary data (eight-byte uint64_t for n follow) + case 0x5F: // Binary data (indefinite length) + { + binary_t b; + return get_cbor_binary(b) && sax->binary(b); + } + + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + case 0x7F: // UTF-8 string (indefinite length) + { + string_t s; + return get_cbor_string(s) && sax->string(s); + } + + // array (0x00..0x17 data items follow) + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + return get_cbor_array(static_cast(static_cast(current) & 0x1Fu), tag_handler); + + case 0x98: // array (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); + } + + case 0x99: // array (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); + } + + case 0x9A: // array (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); + } + + case 0x9B: // array (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); + } + + case 0x9F: // array (indefinite length) + return get_cbor_array(std::size_t(-1), tag_handler); + + // map (0x00..0x17 pairs of data items follow) + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + return get_cbor_object(static_cast(static_cast(current) & 0x1Fu), tag_handler); + + case 0xB8: // map (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); + } + + case 0xB9: // map (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); + } + + case 0xBA: // map (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); + } + + case 0xBB: // map (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); + } + + case 0xBF: // map (indefinite length) + return get_cbor_object(std::size_t(-1), tag_handler); + + case 0xC6: // tagged item + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD8: // tagged item (1 bytes follow) + case 0xD9: // tagged item (2 bytes follow) + case 0xDA: // tagged item (4 bytes follow) + case 0xDB: // tagged item (8 bytes follow) + { + switch (tag_handler) + { + case cbor_tag_handler_t::error: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "invalid byte: 0x" + last_token, "value"))); + } + + case cbor_tag_handler_t::ignore: + { + switch (current) + { + case 0xD8: + { + std::uint8_t len{}; + get_number(input_format_t::cbor, len); + break; + } + case 0xD9: + { + std::uint16_t len{}; + get_number(input_format_t::cbor, len); + break; + } + case 0xDA: + { + std::uint32_t len{}; + get_number(input_format_t::cbor, len); + break; + } + case 0xDB: + { + std::uint64_t len{}; + get_number(input_format_t::cbor, len); + break; + } + default: + break; + } + return parse_cbor_internal(true, tag_handler); + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + } + + case 0xF4: // false + return sax->boolean(false); + + case 0xF5: // true + return sax->boolean(true); + + case 0xF6: // null + return sax->null(); + + case 0xF9: // Half-Precision Float (two-byte IEEE 754) + { + const auto byte1_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) + { + return false; + } + const auto byte2_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) + { + return false; + } + + const auto byte1 = static_cast(byte1_raw); + const auto byte2 = static_cast(byte2_raw); + + // code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added + // to IEEE 754 in 2008, today's programming platforms often + // still only have limited support for them. It is very + // easy to include at least decoding support for them even + // without such support. An example of a small decoder for + // half-precision floating-point numbers in the C language + // is shown in Fig. 3. + const auto half = static_cast((byte1 << 8u) + byte2); + const double val = [&half] + { + const int exp = (half >> 10u) & 0x1Fu; + const unsigned int mant = half & 0x3FFu; + JSON_ASSERT(0 <= exp&& exp <= 32); + JSON_ASSERT(mant <= 1024); + switch (exp) + { + case 0: + return std::ldexp(mant, -24); + case 31: + return (mant == 0) + ? std::numeric_limits::infinity() + : std::numeric_limits::quiet_NaN(); + default: + return std::ldexp(mant + 1024, exp - 25); + } + }(); + return sax->number_float((half & 0x8000u) != 0 + ? static_cast(-val) + : static_cast(val), ""); + } + + case 0xFA: // Single-Precision Float (four-byte IEEE 754) + { + float number{}; + return get_number(input_format_t::cbor, number) && sax->number_float(static_cast(number), ""); + } + + case 0xFB: // Double-Precision Float (eight-byte IEEE 754) + { + double number{}; + return get_number(input_format_t::cbor, number) && sax->number_float(static_cast(number), ""); + } + + default: // anything else (0xFF is handled inside the other types) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "invalid byte: 0x" + last_token, "value"))); + } + } + } + + /*! + @brief reads a CBOR string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + Additionally, CBOR's strings with indefinite lengths are supported. + + @param[out] result created string + + @return whether string creation completed + */ + bool get_cbor_string(string_t& result) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "string"))) + { + return false; + } + + switch (current) + { + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + { + return get_string(input_format_t::cbor, static_cast(current) & 0x1Fu, result); + } + + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x7F: // UTF-8 string (indefinite length) + { + while (get() != 0xFF) + { + string_t chunk; + if (!get_cbor_string(chunk)) + { + return false; + } + result.append(chunk); + } + return true; + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x" + last_token, "string"))); + } + } + } + + /*! + @brief reads a CBOR byte array + + This function first reads starting bytes to determine the expected + byte array length and then copies this number of bytes into the byte array. + Additionally, CBOR's byte arrays with indefinite lengths are supported. + + @param[out] result created byte array + + @return whether byte array creation completed + */ + bool get_cbor_binary(binary_t& result) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "binary"))) + { + return false; + } + + switch (current) + { + // Binary data (0x00..0x17 bytes follow) + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + { + return get_binary(input_format_t::cbor, static_cast(current) & 0x1Fu, result); + } + + case 0x58: // Binary data (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x59: // Binary data (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x5A: // Binary data (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x5B: // Binary data (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x5F: // Binary data (indefinite length) + { + while (get() != 0xFF) + { + binary_t chunk; + if (!get_cbor_binary(chunk)) + { + return false; + } + result.insert(result.end(), chunk.begin(), chunk.end()); + } + return true; + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x" + last_token, "binary"))); + } + } + } + + /*! + @param[in] len the length of the array or std::size_t(-1) for an + array of indefinite size + @param[in] tag_handler how CBOR tags should be treated + @return whether array creation completed + */ + bool get_cbor_array(const std::size_t len, + const cbor_tag_handler_t tag_handler) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) + { + return false; + } + + if (len != std::size_t(-1)) + { + for (std::size_t i = 0; i < len; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) + { + return false; + } + } + } + else + { + while (get() != 0xFF) + { + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(false, tag_handler))) + { + return false; + } + } + } + + return sax->end_array(); + } + + /*! + @param[in] len the length of the object or std::size_t(-1) for an + object of indefinite size + @param[in] tag_handler how CBOR tags should be treated + @return whether object creation completed + */ + bool get_cbor_object(const std::size_t len, + const cbor_tag_handler_t tag_handler) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) + { + return false; + } + + string_t key; + if (len != std::size_t(-1)) + { + for (std::size_t i = 0; i < len; ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) + { + return false; + } + key.clear(); + } + } + else + { + while (get() != 0xFF) + { + if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) + { + return false; + } + key.clear(); + } + } + + return sax->end_object(); + } + + ///////////// + // MsgPack // + ///////////// + + /*! + @return whether a valid MessagePack value was passed to the SAX parser + */ + bool parse_msgpack_internal() + { + switch (get()) + { + // EOF + case std::char_traits::eof(): + return unexpect_eof(input_format_t::msgpack, "value"); + + // positive fixint + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + return sax->number_unsigned(static_cast(current)); + + // fixmap + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + return get_msgpack_object(static_cast(static_cast(current) & 0x0Fu)); + + // fixarray + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + case 0x98: + case 0x99: + case 0x9A: + case 0x9B: + case 0x9C: + case 0x9D: + case 0x9E: + case 0x9F: + return get_msgpack_array(static_cast(static_cast(current) & 0x0Fu)); + + // fixstr + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + case 0xD9: // str 8 + case 0xDA: // str 16 + case 0xDB: // str 32 + { + string_t s; + return get_msgpack_string(s) && sax->string(s); + } + + case 0xC0: // nil + return sax->null(); + + case 0xC2: // false + return sax->boolean(false); + + case 0xC3: // true + return sax->boolean(true); + + case 0xC4: // bin 8 + case 0xC5: // bin 16 + case 0xC6: // bin 32 + case 0xC7: // ext 8 + case 0xC8: // ext 16 + case 0xC9: // ext 32 + case 0xD4: // fixext 1 + case 0xD5: // fixext 2 + case 0xD6: // fixext 4 + case 0xD7: // fixext 8 + case 0xD8: // fixext 16 + { + binary_t b; + return get_msgpack_binary(b) && sax->binary(b); + } + + case 0xCA: // float 32 + { + float number{}; + return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast(number), ""); + } + + case 0xCB: // float 64 + { + double number{}; + return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast(number), ""); + } + + case 0xCC: // uint 8 + { + std::uint8_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xCD: // uint 16 + { + std::uint16_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xCE: // uint 32 + { + std::uint32_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xCF: // uint 64 + { + std::uint64_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xD0: // int 8 + { + std::int8_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xD1: // int 16 + { + std::int16_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xD2: // int 32 + { + std::int32_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xD3: // int 64 + { + std::int64_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xDC: // array 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast(len)); + } + + case 0xDD: // array 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast(len)); + } + + case 0xDE: // map 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast(len)); + } + + case 0xDF: // map 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast(len)); + } + + // negative fixint + case 0xE0: + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xED: + case 0xEE: + case 0xEF: + case 0xF0: + case 0xF1: + case 0xF2: + case 0xF3: + case 0xF4: + case 0xF5: + case 0xF6: + case 0xF7: + case 0xF8: + case 0xF9: + case 0xFA: + case 0xFB: + case 0xFC: + case 0xFD: + case 0xFE: + case 0xFF: + return sax->number_integer(static_cast(current)); + + default: // anything else + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::msgpack, "invalid byte: 0x" + last_token, "value"))); + } + } + } + + /*! + @brief reads a MessagePack string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + + @param[out] result created string + + @return whether string creation completed + */ + bool get_msgpack_string(string_t& result) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::msgpack, "string"))) + { + return false; + } + + switch (current) + { + // fixstr + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + { + return get_string(input_format_t::msgpack, static_cast(current) & 0x1Fu, result); + } + + case 0xD9: // str 8 + { + std::uint8_t len{}; + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + } + + case 0xDA: // str 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + } + + case 0xDB: // str 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::msgpack, "expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x" + last_token, "string"))); + } + } + } + + /*! + @brief reads a MessagePack byte array + + This function first reads starting bytes to determine the expected + byte array length and then copies this number of bytes into a byte array. + + @param[out] result created byte array + + @return whether byte array creation completed + */ + bool get_msgpack_binary(binary_t& result) + { + // helper function to set the subtype + auto assign_and_return_true = [&result](std::int8_t subtype) + { + result.set_subtype(static_cast(subtype)); + return true; + }; + + switch (current) + { + case 0xC4: // bin 8 + { + std::uint8_t len{}; + return get_number(input_format_t::msgpack, len) && + get_binary(input_format_t::msgpack, len, result); + } + + case 0xC5: // bin 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && + get_binary(input_format_t::msgpack, len, result); + } + + case 0xC6: // bin 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && + get_binary(input_format_t::msgpack, len, result); + } + + case 0xC7: // ext 8 + { + std::uint8_t len{}; + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, len) && + get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, len, result) && + assign_and_return_true(subtype); + } + + case 0xC8: // ext 16 + { + std::uint16_t len{}; + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, len) && + get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, len, result) && + assign_and_return_true(subtype); + } + + case 0xC9: // ext 32 + { + std::uint32_t len{}; + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, len) && + get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, len, result) && + assign_and_return_true(subtype); + } + + case 0xD4: // fixext 1 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 1, result) && + assign_and_return_true(subtype); + } + + case 0xD5: // fixext 2 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 2, result) && + assign_and_return_true(subtype); + } + + case 0xD6: // fixext 4 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 4, result) && + assign_and_return_true(subtype); + } + + case 0xD7: // fixext 8 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 8, result) && + assign_and_return_true(subtype); + } + + case 0xD8: // fixext 16 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 16, result) && + assign_and_return_true(subtype); + } + + default: // LCOV_EXCL_LINE + return false; // LCOV_EXCL_LINE + } + } + + /*! + @param[in] len the length of the array + @return whether array creation completed + */ + bool get_msgpack_array(const std::size_t len) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) + { + return false; + } + + for (std::size_t i = 0; i < len; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) + { + return false; + } + } + + return sax->end_array(); + } + + /*! + @param[in] len the length of the object + @return whether object creation completed + */ + bool get_msgpack_object(const std::size_t len) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) + { + return false; + } + + string_t key; + for (std::size_t i = 0; i < len; ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) + { + return false; + } + key.clear(); + } + + return sax->end_object(); + } + + //////////// + // UBJSON // + //////////// + + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return whether a valid UBJSON value was passed to the SAX parser + */ + bool parse_ubjson_internal(const bool get_char = true) + { + return get_ubjson_value(get_char ? get_ignore_noop() : current); + } + + /*! + @brief reads a UBJSON string + + This function is either called after reading the 'S' byte explicitly + indicating a string, or in case of an object key where the 'S' byte can be + left out. + + @param[out] result created string + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return whether string creation completed + */ + bool get_ubjson_string(string_t& result, const bool get_char = true) + { + if (get_char) + { + get(); // TODO(niels): may we ignore N here? + } + + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) + { + return false; + } + + switch (current) + { + case 'U': + { + std::uint8_t len{}; + return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + } + + case 'i': + { + std::int8_t len{}; + return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + } + + case 'I': + { + std::int16_t len{}; + return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + } + + case 'l': + { + std::int32_t len{}; + return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + } + + case 'L': + { + std::int64_t len{}; + return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + } + + default: + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token, "string"))); + } + } + + /*! + @param[out] result determined size + @return whether size determination completed + */ + bool get_ubjson_size_value(std::size_t& result) + { + switch (get_ignore_noop()) + { + case 'U': + { + std::uint8_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + case 'i': + { + std::int8_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + case 'I': + { + std::int16_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + case 'l': + { + std::int32_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + case 'L': + { + std::int64_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token, "size"))); + } + } + } + + /*! + @brief determine the type and size for a container + + In the optimized UBJSON format, a type and a size can be provided to allow + for a more compact representation. + + @param[out] result pair of the size and the type + + @return whether pair creation completed + */ + bool get_ubjson_size_type(std::pair& result) + { + result.first = string_t::npos; // size + result.second = 0; // type + + get_ignore_noop(); + + if (current == '$') + { + result.second = get(); // must not ignore 'N', because 'N' maybe the type + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "type"))) + { + return false; + } + + get_ignore_noop(); + if (JSON_HEDLEY_UNLIKELY(current != '#')) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) + { + return false; + } + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "expected '#' after type information; last byte: 0x" + last_token, "size"))); + } + + return get_ubjson_size_value(result.first); + } + + if (current == '#') + { + return get_ubjson_size_value(result.first); + } + + return true; + } + + /*! + @param prefix the previously read or set type prefix + @return whether value creation completed + */ + bool get_ubjson_value(const char_int_type prefix) + { + switch (prefix) + { + case std::char_traits::eof(): // EOF + return unexpect_eof(input_format_t::ubjson, "value"); + + case 'T': // true + return sax->boolean(true); + case 'F': // false + return sax->boolean(false); + + case 'Z': // null + return sax->null(); + + case 'U': + { + std::uint8_t number{}; + return get_number(input_format_t::ubjson, number) && sax->number_unsigned(number); + } + + case 'i': + { + std::int8_t number{}; + return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + } + + case 'I': + { + std::int16_t number{}; + return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + } + + case 'l': + { + std::int32_t number{}; + return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + } + + case 'L': + { + std::int64_t number{}; + return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + } + + case 'd': + { + float number{}; + return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); + } + + case 'D': + { + double number{}; + return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); + } + + case 'H': + { + return get_ubjson_high_precision_number(); + } + + case 'C': // char + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "char"))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(current > 127)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + last_token, "char"))); + } + string_t s(1, static_cast(current)); + return sax->string(s); + } + + case 'S': // string + { + string_t s; + return get_ubjson_string(s) && sax->string(s); + } + + case '[': // array + return get_ubjson_array(); + + case '{': // object + return get_ubjson_object(); + + default: // anything else + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "invalid byte: 0x" + last_token, "value"))); + } + } + } + + /*! + @return whether array creation completed + */ + bool get_ubjson_array() + { + std::pair size_and_type; + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) + { + return false; + } + + if (size_and_type.first != string_t::npos) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) + { + return false; + } + + if (size_and_type.second != 0) + { + if (size_and_type.second != 'N') + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) + { + return false; + } + } + } + } + else + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) + { + return false; + } + } + } + } + else + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) + { + return false; + } + + while (current != ']') + { + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal(false))) + { + return false; + } + get_ignore_noop(); + } + } + + return sax->end_array(); + } + + /*! + @return whether object creation completed + */ + bool get_ubjson_object() + { + std::pair size_and_type; + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) + { + return false; + } + + string_t key; + if (size_and_type.first != string_t::npos) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(size_and_type.first))) + { + return false; + } + + if (size_and_type.second != 0) + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) + { + return false; + } + key.clear(); + } + } + else + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) + { + return false; + } + key.clear(); + } + } + } + else + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) + { + return false; + } + + while (current != '}') + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false) || !sax->key(key))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) + { + return false; + } + get_ignore_noop(); + key.clear(); + } + } + + return sax->end_object(); + } + + // Note, no reader for UBJSON binary types is implemented because they do + // not exist + + bool get_ubjson_high_precision_number() + { + // get size of following number string + std::size_t size{}; + auto res = get_ubjson_size_value(size); + if (JSON_HEDLEY_UNLIKELY(!res)) + { + return res; + } + + // get number string + std::vector number_vector; + for (std::size_t i = 0; i < size; ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "number"))) + { + return false; + } + number_vector.push_back(static_cast(current)); + } + + // parse number string + auto number_ia = detail::input_adapter(std::forward(number_vector)); + auto number_lexer = detail::lexer(std::move(number_ia), false); + const auto result_number = number_lexer.scan(); + const auto number_string = number_lexer.get_token_string(); + const auto result_remainder = number_lexer.scan(); + + using token_type = typename detail::lexer_base::token_type; + + if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input)) + { + return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, exception_message(input_format_t::ubjson, "invalid number text: " + number_lexer.get_token_string(), "high-precision number"))); + } + + switch (result_number) + { + case token_type::value_integer: + return sax->number_integer(number_lexer.get_number_integer()); + case token_type::value_unsigned: + return sax->number_unsigned(number_lexer.get_number_unsigned()); + case token_type::value_float: + return sax->number_float(number_lexer.get_number_float(), std::move(number_string)); + default: + return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, exception_message(input_format_t::ubjson, "invalid number text: " + number_lexer.get_token_string(), "high-precision number"))); + } + } + + /////////////////////// + // Utility functions // + /////////////////////// + + /*! + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a -'ve valued + `std::char_traits::eof()` in that case. + + @return character read from the input + */ + char_int_type get() + { + ++chars_read; + return current = ia.get_character(); + } + + /*! + @return character read from the input after ignoring all 'N' entries + */ + char_int_type get_ignore_noop() + { + do + { + get(); + } + while (current == 'N'); + + return current; + } + + /* + @brief read a number from the input + + @tparam NumberType the type of the number + @param[in] format the current format (for diagnostics) + @param[out] result number of type @a NumberType + + @return whether conversion completed + + @note This function needs to respect the system's endianess, because + bytes in CBOR, MessagePack, and UBJSON are stored in network order + (big endian) and therefore need reordering on little endian systems. + */ + template + bool get_number(const input_format_t format, NumberType& result) + { + // step 1: read input into array with system's byte order + std::array vec; + for (std::size_t i = 0; i < sizeof(NumberType); ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "number"))) + { + return false; + } + + // reverse byte order prior to conversion if necessary + if (is_little_endian != InputIsLittleEndian) + { + vec[sizeof(NumberType) - i - 1] = static_cast(current); + } + else + { + vec[i] = static_cast(current); // LCOV_EXCL_LINE + } + } + + // step 2: convert array into number of type T and return + std::memcpy(&result, vec.data(), sizeof(NumberType)); + return true; + } + + /*! + @brief create a string by reading characters from the input + + @tparam NumberType the type of the number + @param[in] format the current format (for diagnostics) + @param[in] len number of characters to read + @param[out] result string created by reading @a len bytes + + @return whether string creation completed + + @note We can not reserve @a len bytes for the result, because @a len + may be too large. Usually, @ref unexpect_eof() detects the end of + the input before we run out of string memory. + */ + template + bool get_string(const input_format_t format, + const NumberType len, + string_t& result) + { + bool success = true; + for (NumberType i = 0; i < len; i++) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "string"))) + { + success = false; + break; + } + result.push_back(static_cast(current)); + }; + return success; + } + + /*! + @brief create a byte array by reading bytes from the input + + @tparam NumberType the type of the number + @param[in] format the current format (for diagnostics) + @param[in] len number of bytes to read + @param[out] result byte array created by reading @a len bytes + + @return whether byte array creation completed + + @note We can not reserve @a len bytes for the result, because @a len + may be too large. Usually, @ref unexpect_eof() detects the end of + the input before we run out of memory. + */ + template + bool get_binary(const input_format_t format, + const NumberType len, + binary_t& result) + { + bool success = true; + for (NumberType i = 0; i < len; i++) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "binary"))) + { + success = false; + break; + } + result.push_back(static_cast(current)); + } + return success; + } + + /*! + @param[in] format the current format (for diagnostics) + @param[in] context further context information (for diagnostics) + @return whether the last read character is not EOF + */ + JSON_HEDLEY_NON_NULL(3) + bool unexpect_eof(const input_format_t format, const char* context) const + { + if (JSON_HEDLEY_UNLIKELY(current == std::char_traits::eof())) + { + return sax->parse_error(chars_read, "", + parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context))); + } + return true; + } + + /*! + @return a string representation of the last read byte + */ + std::string get_token_string() const + { + std::array cr{{}}; + (std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(current)); + return std::string{cr.data()}; + } + + /*! + @param[in] format the current format + @param[in] detail a detailed error message + @param[in] context further context information + @return a message string to use in the parse_error exceptions + */ + std::string exception_message(const input_format_t format, + const std::string& detail, + const std::string& context) const + { + std::string error_msg = "syntax error while parsing "; + + switch (format) + { + case input_format_t::cbor: + error_msg += "CBOR"; + break; + + case input_format_t::msgpack: + error_msg += "MessagePack"; + break; + + case input_format_t::ubjson: + error_msg += "UBJSON"; + break; + + case input_format_t::bson: + error_msg += "BSON"; + break; + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + + return error_msg + " " + context + ": " + detail; + } + + private: + /// input adapter + InputAdapterType ia; + + /// the current character + char_int_type current = std::char_traits::eof(); + + /// the number of characters read + std::size_t chars_read = 0; + + /// whether we can assume little endianess + const bool is_little_endian = little_endianess(); + + /// the SAX parser + json_sax_t* sax = nullptr; +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + +// #include + + +#include // isfinite +#include // uint8_t +#include // function +#include // string +#include // move +#include // vector + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +//////////// +// parser // +//////////// + +enum class parse_event_t : uint8_t +{ + /// the parser read `{` and started to process a JSON object + object_start, + /// the parser read `}` and finished processing a JSON object + object_end, + /// the parser read `[` and started to process a JSON array + array_start, + /// the parser read `]` and finished processing a JSON array + array_end, + /// the parser read a key of a value in an object + key, + /// the parser finished reading a JSON value + value +}; + +template +using parser_callback_t = + std::function; + +/*! +@brief syntax analysis + +This class implements a recursive descent parser. +*/ +template +class parser +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using lexer_t = lexer; + using token_type = typename lexer_t::token_type; + + public: + /// a parser reading from an input adapter + explicit parser(InputAdapterType&& adapter, + const parser_callback_t cb = nullptr, + const bool allow_exceptions_ = true, + const bool skip_comments = false) + : callback(cb) + , m_lexer(std::move(adapter), skip_comments) + , allow_exceptions(allow_exceptions_) + { + // read first token + get_token(); + } + + /*! + @brief public parser interface + + @param[in] strict whether to expect the last token to be EOF + @param[in,out] result parsed JSON value + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + void parse(const bool strict, BasicJsonType& result) + { + if (callback) + { + json_sax_dom_callback_parser sdp(result, callback, allow_exceptions); + sax_parse_internal(&sdp); + result.assert_invariant(); + + // in strict mode, input must be completely read + if (strict && (get_token() != token_type::end_of_input)) + { + sdp.parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::end_of_input, "value"))); + } + + // in case of an error, return discarded value + if (sdp.is_errored()) + { + result = value_t::discarded; + return; + } + + // set top-level value to null if it was discarded by the callback + // function + if (result.is_discarded()) + { + result = nullptr; + } + } + else + { + json_sax_dom_parser sdp(result, allow_exceptions); + sax_parse_internal(&sdp); + result.assert_invariant(); + + // in strict mode, input must be completely read + if (strict && (get_token() != token_type::end_of_input)) + { + sdp.parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::end_of_input, "value"))); + } + + // in case of an error, return discarded value + if (sdp.is_errored()) + { + result = value_t::discarded; + return; + } + } + } + + /*! + @brief public accept interface + + @param[in] strict whether to expect the last token to be EOF + @return whether the input is a proper JSON text + */ + bool accept(const bool strict = true) + { + json_sax_acceptor sax_acceptor; + return sax_parse(&sax_acceptor, strict); + } + + template + JSON_HEDLEY_NON_NULL(2) + bool sax_parse(SAX* sax, const bool strict = true) + { + (void)detail::is_sax_static_asserts {}; + const bool result = sax_parse_internal(sax); + + // strict mode: next byte must be EOF + if (result && strict && (get_token() != token_type::end_of_input)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::end_of_input, "value"))); + } + + return result; + } + + private: + template + JSON_HEDLEY_NON_NULL(2) + bool sax_parse_internal(SAX* sax) + { + // stack to remember the hierarchy of structured values we are parsing + // true = array; false = object + std::vector states; + // value to avoid a goto (see comment where set to true) + bool skip_to_state_evaluation = false; + + while (true) + { + if (!skip_to_state_evaluation) + { + // invariant: get_token() was called before each iteration + switch (last_token) + { + case token_type::begin_object: + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) + { + return false; + } + + // closing } -> we are done + if (get_token() == token_type::end_object) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) + { + return false; + } + break; + } + + // parse key + if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::value_string, "object key"))); + } + if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) + { + return false; + } + + // parse separator (:) + if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::name_separator, "object separator"))); + } + + // remember we are now inside an object + states.push_back(false); + + // parse values + get_token(); + continue; + } + + case token_type::begin_array: + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) + { + return false; + } + + // closing ] -> we are done + if (get_token() == token_type::end_array) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) + { + return false; + } + break; + } + + // remember we are now inside an array + states.push_back(true); + + // parse values (no need to call get_token) + continue; + } + + case token_type::value_float: + { + const auto res = m_lexer.get_number_float(); + + if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res))) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + out_of_range::create(406, "number overflow parsing '" + m_lexer.get_token_string() + "'")); + } + + if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) + { + return false; + } + + break; + } + + case token_type::literal_false: + { + if (JSON_HEDLEY_UNLIKELY(!sax->boolean(false))) + { + return false; + } + break; + } + + case token_type::literal_null: + { + if (JSON_HEDLEY_UNLIKELY(!sax->null())) + { + return false; + } + break; + } + + case token_type::literal_true: + { + if (JSON_HEDLEY_UNLIKELY(!sax->boolean(true))) + { + return false; + } + break; + } + + case token_type::value_integer: + { + if (JSON_HEDLEY_UNLIKELY(!sax->number_integer(m_lexer.get_number_integer()))) + { + return false; + } + break; + } + + case token_type::value_string: + { + if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string()))) + { + return false; + } + break; + } + + case token_type::value_unsigned: + { + if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(m_lexer.get_number_unsigned()))) + { + return false; + } + break; + } + + case token_type::parse_error: + { + // using "uninitialized" to avoid "expected" message + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::uninitialized, "value"))); + } + + default: // the last token was unexpected + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::literal_or_value, "value"))); + } + } + } + else + { + skip_to_state_evaluation = false; + } + + // we reached this line after we successfully parsed a value + if (states.empty()) + { + // empty stack: we reached the end of the hierarchy: done + return true; + } + + if (states.back()) // array + { + // comma -> next value + if (get_token() == token_type::value_separator) + { + // parse a new value + get_token(); + continue; + } + + // closing ] + if (JSON_HEDLEY_LIKELY(last_token == token_type::end_array)) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) + { + return false; + } + + // We are done with this array. Before we can parse a + // new value, we need to evaluate the new state first. + // By setting skip_to_state_evaluation to false, we + // are effectively jumping to the beginning of this if. + JSON_ASSERT(!states.empty()); + states.pop_back(); + skip_to_state_evaluation = true; + continue; + } + + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::end_array, "array"))); + } + else // object + { + // comma -> next value + if (get_token() == token_type::value_separator) + { + // parse key + if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::value_string)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::value_string, "object key"))); + } + + if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) + { + return false; + } + + // parse separator (:) + if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::name_separator, "object separator"))); + } + + // parse values + get_token(); + continue; + } + + // closing } + if (JSON_HEDLEY_LIKELY(last_token == token_type::end_object)) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) + { + return false; + } + + // We are done with this object. Before we can parse a + // new value, we need to evaluate the new state first. + // By setting skip_to_state_evaluation to false, we + // are effectively jumping to the beginning of this if. + JSON_ASSERT(!states.empty()); + states.pop_back(); + skip_to_state_evaluation = true; + continue; + } + + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::end_object, "object"))); + } + } + } + + /// get next token from lexer + token_type get_token() + { + return last_token = m_lexer.scan(); + } + + std::string exception_message(const token_type expected, const std::string& context) + { + std::string error_msg = "syntax error "; + + if (!context.empty()) + { + error_msg += "while parsing " + context + " "; + } + + error_msg += "- "; + + if (last_token == token_type::parse_error) + { + error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" + + m_lexer.get_token_string() + "'"; + } + else + { + error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token)); + } + + if (expected != token_type::uninitialized) + { + error_msg += "; expected " + std::string(lexer_t::token_type_name(expected)); + } + + return error_msg; + } + + private: + /// callback function + const parser_callback_t callback = nullptr; + /// the type of the last read token + token_type last_token = token_type::uninitialized; + /// the lexer + lexer_t m_lexer; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; +}; +} // namespace detail +} // namespace nlohmann + +// #include + + +// #include + + +#include // ptrdiff_t +#include // numeric_limits + +namespace nlohmann +{ +namespace detail +{ +/* +@brief an iterator for primitive JSON types + +This class models an iterator for primitive JSON types (boolean, number, +string). It's only purpose is to allow the iterator/const_iterator classes +to "iterate" over primitive values. Internally, the iterator is modeled by +a `difference_type` variable. Value begin_value (`0`) models the begin, +end_value (`1`) models past the end. +*/ +class primitive_iterator_t +{ + private: + using difference_type = std::ptrdiff_t; + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value + 1; + + /// iterator as signed integer type + difference_type m_it = (std::numeric_limits::min)(); + + public: + constexpr difference_type get_value() const noexcept + { + return m_it; + } + + /// set iterator to a defined beginning + void set_begin() noexcept + { + m_it = begin_value; + } + + /// set iterator to a defined past the end + void set_end() noexcept + { + m_it = end_value; + } + + /// return whether the iterator can be dereferenced + constexpr bool is_begin() const noexcept + { + return m_it == begin_value; + } + + /// return whether the iterator is at end + constexpr bool is_end() const noexcept + { + return m_it == end_value; + } + + friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it == rhs.m_it; + } + + friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it < rhs.m_it; + } + + primitive_iterator_t operator+(difference_type n) noexcept + { + auto result = *this; + result += n; + return result; + } + + friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it - rhs.m_it; + } + + primitive_iterator_t& operator++() noexcept + { + ++m_it; + return *this; + } + + primitive_iterator_t const operator++(int) noexcept + { + auto result = *this; + ++m_it; + return result; + } + + primitive_iterator_t& operator--() noexcept + { + --m_it; + return *this; + } + + primitive_iterator_t const operator--(int) noexcept + { + auto result = *this; + --m_it; + return result; + } + + primitive_iterator_t& operator+=(difference_type n) noexcept + { + m_it += n; + return *this; + } + + primitive_iterator_t& operator-=(difference_type n) noexcept + { + m_it -= n; + return *this; + } +}; +} // namespace detail +} // namespace nlohmann + + +namespace nlohmann +{ +namespace detail +{ +/*! +@brief an iterator value + +@note This structure could easily be a union, but MSVC currently does not allow +unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. +*/ +template struct internal_iterator +{ + /// iterator for JSON objects + typename BasicJsonType::object_t::iterator object_iterator {}; + /// iterator for JSON arrays + typename BasicJsonType::array_t::iterator array_iterator {}; + /// generic iterator for all other types + primitive_iterator_t primitive_iterator {}; +}; +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next +#include // conditional, is_const, remove_const + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +// forward declare, to be able to friend it later on +template class iteration_proxy; +template class iteration_proxy_value; + +/*! +@brief a template for a bidirectional iterator for the @ref basic_json class +This class implements a both iterators (iterator and const_iterator) for the +@ref basic_json class. +@note An iterator is called *initialized* when a pointer to a JSON value has + been set (e.g., by a constructor or a copy assignment). If the iterator is + default-constructed, it is *uninitialized* and most methods are undefined. + **The library uses assertions to detect calls on uninitialized iterators.** +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). +@since version 1.0.0, simplified in version 2.0.9, change to bidirectional + iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) +*/ +template +class iter_impl +{ + /// allow basic_json to access private members + friend iter_impl::value, typename std::remove_const::type, const BasicJsonType>::type>; + friend BasicJsonType; + friend iteration_proxy; + friend iteration_proxy_value; + + using object_t = typename BasicJsonType::object_t; + using array_t = typename BasicJsonType::array_t; + // make sure BasicJsonType is basic_json or const basic_json + static_assert(is_basic_json::type>::value, + "iter_impl only accepts (const) basic_json"); + + public: + + /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. + /// The C++ Standard has never required user-defined iterators to derive from std::iterator. + /// A user-defined iterator should provide publicly accessible typedefs named + /// iterator_category, value_type, difference_type, pointer, and reference. + /// Note that value_type is required to be non-const, even for constant iterators. + using iterator_category = std::bidirectional_iterator_tag; + + /// the type of the values when the iterator is dereferenced + using value_type = typename BasicJsonType::value_type; + /// a type to represent differences between iterators + using difference_type = typename BasicJsonType::difference_type; + /// defines a pointer to the type iterated over (value_type) + using pointer = typename std::conditional::value, + typename BasicJsonType::const_pointer, + typename BasicJsonType::pointer>::type; + /// defines a reference to the type iterated over (value_type) + using reference = + typename std::conditional::value, + typename BasicJsonType::const_reference, + typename BasicJsonType::reference>::type; + + /// default constructor + iter_impl() = default; + + /*! + @brief constructor for a given JSON instance + @param[in] object pointer to a JSON object for this iterator + @pre object != nullptr + @post The iterator is initialized; i.e. `m_object != nullptr`. + */ + explicit iter_impl(pointer object) noexcept : m_object(object) + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = typename object_t::iterator(); + break; + } + + case value_t::array: + { + m_it.array_iterator = typename array_t::iterator(); + break; + } + + default: + { + m_it.primitive_iterator = primitive_iterator_t(); + break; + } + } + } + + /*! + @note The conventional copy constructor and copy assignment are implicitly + defined. Combined with the following converting constructor and + assignment, they support: (1) copy from iterator to iterator, (2) + copy from const iterator to const iterator, and (3) conversion from + iterator to const iterator. However conversion from const iterator + to iterator is not defined. + */ + + /*! + @brief const copy constructor + @param[in] other const iterator to copy from + @note This copy constructor had to be defined explicitly to circumvent a bug + occurring on msvc v19.0 compiler (VS 2015) debug build. For more + information refer to: https://github.com/nlohmann/json/issues/1608 + */ + iter_impl(const iter_impl& other) noexcept + : m_object(other.m_object), m_it(other.m_it) + {} + + /*! + @brief converting assignment + @param[in] other const iterator to copy from + @return const/non-const iterator + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(const iter_impl& other) noexcept + { + m_object = other.m_object; + m_it = other.m_it; + return *this; + } + + /*! + @brief converting constructor + @param[in] other non-const iterator to copy from + @note It is not checked whether @a other is initialized. + */ + iter_impl(const iter_impl::type>& other) noexcept + : m_object(other.m_object), m_it(other.m_it) + {} + + /*! + @brief converting assignment + @param[in] other non-const iterator to copy from + @return const/non-const iterator + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(const iter_impl::type>& other) noexcept + { + m_object = other.m_object; + m_it = other.m_it; + return *this; + } + + private: + /*! + @brief set the iterator to the first value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_begin() noexcept + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_value.object->begin(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_value.array->begin(); + break; + } + + case value_t::null: + { + // set to end so begin()==end() is true: null is empty + m_it.primitive_iterator.set_end(); + break; + } + + default: + { + m_it.primitive_iterator.set_begin(); + break; + } + } + } + + /*! + @brief set the iterator past the last value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_end() noexcept + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_value.object->end(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_value.array->end(); + break; + } + + default: + { + m_it.primitive_iterator.set_end(); + break; + } + } + } + + public: + /*! + @brief return a reference to the value pointed to by the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator*() const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end()); + return m_it.object_iterator->second; + } + + case value_t::array: + { + JSON_ASSERT(m_it.array_iterator != m_object->m_value.array->end()); + return *m_it.array_iterator; + } + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: + { + if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief dereference the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + pointer operator->() const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end()); + return &(m_it.object_iterator->second); + } + + case value_t::array: + { + JSON_ASSERT(m_it.array_iterator != m_object->m_value.array->end()); + return &*m_it.array_iterator; + } + + default: + { + if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) + { + return m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief post-increment (it++) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl const operator++(int) + { + auto result = *this; + ++(*this); + return result; + } + + /*! + @brief pre-increment (++it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator++() + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, 1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, 1); + break; + } + + default: + { + ++m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief post-decrement (it--) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl const operator--(int) + { + auto result = *this; + --(*this); + return result; + } + + /*! + @brief pre-decrement (--it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator--() + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, -1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, -1); + break; + } + + default: + { + --m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief comparison: equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator==(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + return (m_it.object_iterator == other.m_it.object_iterator); + + case value_t::array: + return (m_it.array_iterator == other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator == other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: not equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator!=(const iter_impl& other) const + { + return !operator==(other); + } + + /*! + @brief comparison: smaller + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); + + case value_t::array: + return (m_it.array_iterator < other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator < other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: less than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<=(const iter_impl& other) const + { + return !other.operator < (*this); + } + + /*! + @brief comparison: greater than + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>(const iter_impl& other) const + { + return !operator<=(other); + } + + /*! + @brief comparison: greater than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>=(const iter_impl& other) const + { + return !operator<(other); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator+=(difference_type i) + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: + { + std::advance(m_it.array_iterator, i); + break; + } + + default: + { + m_it.primitive_iterator += i; + break; + } + } + + return *this; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator-=(difference_type i) + { + return operator+=(-i); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /*! + @brief addition of distance and iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + friend iter_impl operator+(difference_type i, const iter_impl& it) + { + auto result = it; + result += i; + return result; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /*! + @brief return difference + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + difference_type operator-(const iter_impl& other) const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: + return m_it.array_iterator - other.m_it.array_iterator; + + default: + return m_it.primitive_iterator - other.m_it.primitive_iterator; + } + } + + /*! + @brief access to successor + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator[](difference_type n) const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); + + case value_t::array: + return *std::next(m_it.array_iterator, n); + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: + { + if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.get_value() == -n)) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief return the key of an object iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const typename object_t::key_type& key() const + { + JSON_ASSERT(m_object != nullptr); + + if (JSON_HEDLEY_LIKELY(m_object->is_object())) + { + return m_it.object_iterator->first; + } + + JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); + } + + /*! + @brief return the value of an iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference value() const + { + return operator*(); + } + + private: + /// associated JSON instance + pointer m_object = nullptr; + /// the actual iterator of the associated instance + internal_iterator::type> m_it {}; +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + + +#include // ptrdiff_t +#include // reverse_iterator +#include // declval + +namespace nlohmann +{ +namespace detail +{ +////////////////////// +// reverse_iterator // +////////////////////// + +/*! +@brief a template for a reverse iterator class + +@tparam Base the base iterator type to reverse. Valid types are @ref +iterator (to create @ref reverse_iterator) and @ref const_iterator (to +create @ref const_reverse_iterator). + +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). +- [OutputIterator](https://en.cppreference.com/w/cpp/named_req/OutputIterator): + It is possible to write to the pointed-to element (only if @a Base is + @ref iterator). + +@since version 1.0.0 +*/ +template +class json_reverse_iterator : public std::reverse_iterator +{ + public: + using difference_type = std::ptrdiff_t; + /// shortcut to the reverse iterator adapter + using base_iterator = std::reverse_iterator; + /// the reference type for the pointed-to element + using reference = typename Base::reference; + + /// create reverse iterator from iterator + explicit json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept + : base_iterator(it) {} + + /// create reverse iterator from base class + explicit json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} + + /// post-increment (it++) + json_reverse_iterator const operator++(int) + { + return static_cast(base_iterator::operator++(1)); + } + + /// pre-increment (++it) + json_reverse_iterator& operator++() + { + return static_cast(base_iterator::operator++()); + } + + /// post-decrement (it--) + json_reverse_iterator const operator--(int) + { + return static_cast(base_iterator::operator--(1)); + } + + /// pre-decrement (--it) + json_reverse_iterator& operator--() + { + return static_cast(base_iterator::operator--()); + } + + /// add to iterator + json_reverse_iterator& operator+=(difference_type i) + { + return static_cast(base_iterator::operator+=(i)); + } + + /// add to iterator + json_reverse_iterator operator+(difference_type i) const + { + return static_cast(base_iterator::operator+(i)); + } + + /// subtract from iterator + json_reverse_iterator operator-(difference_type i) const + { + return static_cast(base_iterator::operator-(i)); + } + + /// return difference + difference_type operator-(const json_reverse_iterator& other) const + { + return base_iterator(*this) - base_iterator(other); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } + + /// return the key of an object iterator + auto key() const -> decltype(std::declval().key()) + { + auto it = --this->base(); + return it.key(); + } + + /// return the value of an iterator + reference value() const + { + auto it = --this->base(); + return it.operator * (); + } +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + + +#include // all_of +#include // isdigit +#include // max +#include // accumulate +#include // string +#include // move +#include // vector + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +template +class json_pointer +{ + // allow basic_json to access private members + NLOHMANN_BASIC_JSON_TPL_DECLARATION + friend class basic_json; + + public: + /*! + @brief create JSON pointer + + Create a JSON pointer according to the syntax described in + [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). + + @param[in] s string representing the JSON pointer; if omitted, the empty + string is assumed which references the whole JSON value + + @throw parse_error.107 if the given JSON pointer @a s is nonempty and does + not begin with a slash (`/`); see example below + + @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is + not followed by `0` (representing `~`) or `1` (representing `/`); see + example below + + @liveexample{The example shows the construction several valid JSON pointers + as well as the exceptional behavior.,json_pointer} + + @since version 2.0.0 + */ + explicit json_pointer(const std::string& s = "") + : reference_tokens(split(s)) + {} + + /*! + @brief return a string representation of the JSON pointer + + @invariant For each JSON pointer `ptr`, it holds: + @code {.cpp} + ptr == json_pointer(ptr.to_string()); + @endcode + + @return a string representation of the JSON pointer + + @liveexample{The example shows the result of `to_string`.,json_pointer__to_string} + + @since version 2.0.0 + */ + std::string to_string() const + { + return std::accumulate(reference_tokens.begin(), reference_tokens.end(), + std::string{}, + [](const std::string & a, const std::string & b) + { + return a + "/" + escape(b); + }); + } + + /// @copydoc to_string() + operator std::string() const + { + return to_string(); + } + + /*! + @brief append another JSON pointer at the end of this JSON pointer + + @param[in] ptr JSON pointer to append + @return JSON pointer with @a ptr appended + + @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} + + @complexity Linear in the length of @a ptr. + + @sa @ref operator/=(std::string) to append a reference token + @sa @ref operator/=(std::size_t) to append an array index + @sa @ref operator/(const json_pointer&, const json_pointer&) for a binary operator + + @since version 3.6.0 + */ + json_pointer& operator/=(const json_pointer& ptr) + { + reference_tokens.insert(reference_tokens.end(), + ptr.reference_tokens.begin(), + ptr.reference_tokens.end()); + return *this; + } + + /*! + @brief append an unescaped reference token at the end of this JSON pointer + + @param[in] token reference token to append + @return JSON pointer with @a token appended without escaping @a token + + @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} + + @complexity Amortized constant. + + @sa @ref operator/=(const json_pointer&) to append a JSON pointer + @sa @ref operator/=(std::size_t) to append an array index + @sa @ref operator/(const json_pointer&, std::size_t) for a binary operator + + @since version 3.6.0 + */ + json_pointer& operator/=(std::string token) + { + push_back(std::move(token)); + return *this; + } + + /*! + @brief append an array index at the end of this JSON pointer + + @param[in] array_idx array index to append + @return JSON pointer with @a array_idx appended + + @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} + + @complexity Amortized constant. + + @sa @ref operator/=(const json_pointer&) to append a JSON pointer + @sa @ref operator/=(std::string) to append a reference token + @sa @ref operator/(const json_pointer&, std::string) for a binary operator + + @since version 3.6.0 + */ + json_pointer& operator/=(std::size_t array_idx) + { + return *this /= std::to_string(array_idx); + } + + /*! + @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer + + @param[in] lhs JSON pointer + @param[in] rhs JSON pointer + @return a new JSON pointer with @a rhs appended to @a lhs + + @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} + + @complexity Linear in the length of @a lhs and @a rhs. + + @sa @ref operator/=(const json_pointer&) to append a JSON pointer + + @since version 3.6.0 + */ + friend json_pointer operator/(const json_pointer& lhs, + const json_pointer& rhs) + { + return json_pointer(lhs) /= rhs; + } + + /*! + @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer + + @param[in] ptr JSON pointer + @param[in] token reference token + @return a new JSON pointer with unescaped @a token appended to @a ptr + + @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} + + @complexity Linear in the length of @a ptr. + + @sa @ref operator/=(std::string) to append a reference token + + @since version 3.6.0 + */ + friend json_pointer operator/(const json_pointer& ptr, std::string token) + { + return json_pointer(ptr) /= std::move(token); + } + + /*! + @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer + + @param[in] ptr JSON pointer + @param[in] array_idx array index + @return a new JSON pointer with @a array_idx appended to @a ptr + + @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} + + @complexity Linear in the length of @a ptr. + + @sa @ref operator/=(std::size_t) to append an array index + + @since version 3.6.0 + */ + friend json_pointer operator/(const json_pointer& ptr, std::size_t array_idx) + { + return json_pointer(ptr) /= array_idx; + } + + /*! + @brief returns the parent of this JSON pointer + + @return parent of this JSON pointer; in case this JSON pointer is the root, + the root itself is returned + + @complexity Linear in the length of the JSON pointer. + + @liveexample{The example shows the result of `parent_pointer` for different + JSON Pointers.,json_pointer__parent_pointer} + + @since version 3.6.0 + */ + json_pointer parent_pointer() const + { + if (empty()) + { + return *this; + } + + json_pointer res = *this; + res.pop_back(); + return res; + } + + /*! + @brief remove last reference token + + @pre not `empty()` + + @liveexample{The example shows the usage of `pop_back`.,json_pointer__pop_back} + + @complexity Constant. + + @throw out_of_range.405 if JSON pointer has no parent + + @since version 3.6.0 + */ + void pop_back() + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + reference_tokens.pop_back(); + } + + /*! + @brief return last reference token + + @pre not `empty()` + @return last reference token + + @liveexample{The example shows the usage of `back`.,json_pointer__back} + + @complexity Constant. + + @throw out_of_range.405 if JSON pointer has no parent + + @since version 3.6.0 + */ + const std::string& back() const + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + return reference_tokens.back(); + } + + /*! + @brief append an unescaped token at the end of the reference pointer + + @param[in] token token to add + + @complexity Amortized constant. + + @liveexample{The example shows the result of `push_back` for different + JSON Pointers.,json_pointer__push_back} + + @since version 3.6.0 + */ + void push_back(const std::string& token) + { + reference_tokens.push_back(token); + } + + /// @copydoc push_back(const std::string&) + void push_back(std::string&& token) + { + reference_tokens.push_back(std::move(token)); + } + + /*! + @brief return whether pointer points to the root document + + @return true iff the JSON pointer points to the root document + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example shows the result of `empty` for different JSON + Pointers.,json_pointer__empty} + + @since version 3.6.0 + */ + bool empty() const noexcept + { + return reference_tokens.empty(); + } + + private: + /*! + @param[in] s reference token to be converted into an array index + + @return integer representation of @a s + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index begins not with a digit + @throw out_of_range.404 if string @a s could not be converted to an integer + @throw out_of_range.410 if an array index exceeds size_type + */ + static typename BasicJsonType::size_type array_index(const std::string& s) + { + using size_type = typename BasicJsonType::size_type; + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + s + + "' must not begin with '0'")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + s + "' is not a number")); + } + + std::size_t processed_chars = 0; + unsigned long long res = 0; + JSON_TRY + { + res = std::stoull(s, &processed_chars); + } + JSON_CATCH(std::out_of_range&) + { + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); + } + + // check if the string was completely read + if (JSON_HEDLEY_UNLIKELY(processed_chars != s.size())) + { + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); + } + + // only triggered on special platforms (like 32bit), see also + // https://github.com/nlohmann/json/pull/2203 + if (res >= static_cast((std::numeric_limits::max)())) + { + JSON_THROW(detail::out_of_range::create(410, "array index " + s + " exceeds size_type")); // LCOV_EXCL_LINE + } + + return static_cast(res); + } + + json_pointer top() const + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + /*! + @brief create and return a reference to the pointed to value + + @complexity Linear in the number of reference tokens. + + @throw parse_error.109 if array index is not a number + @throw type_error.313 if value cannot be unflattened + */ + BasicJsonType& get_and_create(BasicJsonType& j) const + { + auto result = &j; + + // in case no reference tokens exist, return a reference to the JSON value + // j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->type()) + { + case detail::value_t::null: + { + if (reference_token == "0") + { + // start a new array if reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case detail::value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // create an entry in the array + result = &result->operator[](array_index(reference_token)); + break; + } + + /* + The following code is only reached if there exists a reference + token _and_ the current value is primitive. In this case, we have + an error situation, because primitive values may only occur as + single value; that is, with an empty list of reference tokens. + */ + default: + JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); + } + } + + return *result; + } + + /*! + @brief return a reference to the pointed to value + + @note This version does not throw if a value is not present, but tries to + create nested values instead. For instance, calling this function + with pointer `"/this/that"` on a null value is equivalent to calling + `operator[]("this").operator[]("that")` on that value, effectively + changing the null value to an object. + + @param[in] ptr a JSON value + + @return reference to the JSON value pointed to by the JSON pointer + + @complexity Linear in the length of the JSON pointer. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + BasicJsonType& get_unchecked(BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + // convert null values to arrays or objects before continuing + if (ptr->is_null()) + { + // check if reference token is a number + const bool nums = + std::all_of(reference_token.begin(), reference_token.end(), + [](const unsigned char x) + { + return std::isdigit(x); + }); + + // change value to array for numbers or "-" or to object otherwise + *ptr = (nums || reference_token == "-") + ? detail::value_t::array + : detail::value_t::object; + } + + switch (ptr->type()) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (reference_token == "-") + { + // explicitly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + ptr = &ptr->operator[](array_index(reference_token)); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + BasicJsonType& get_checked(BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // note: at performs range check + ptr = &ptr->at(array_index(reference_token)); + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @brief return a const reference to the pointed to value + + @param[in] ptr a JSON value + + @return const reference to the JSON value pointed to by the JSON + pointer + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" cannot be used for const access + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // use unchecked array access + ptr = &ptr->operator[](array_index(reference_token)); + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + const BasicJsonType& get_checked(const BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // note: at performs range check + ptr = &ptr->at(array_index(reference_token)); + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + */ + bool contains(const BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + if (!ptr->contains(reference_token)) + { + // we did not find the key in the object + return false; + } + + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + return false; + } + if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9"))) + { + // invalid char + return false; + } + if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1)) + { + if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9'))) + { + // first char should be between '1' and '9' + return false; + } + for (std::size_t i = 1; i < reference_token.size(); i++) + { + if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9'))) + { + // other char should be between '0' and '9' + return false; + } + } + } + + const auto idx = array_index(reference_token); + if (idx >= ptr->size()) + { + // index out of range + return false; + } + + ptr = &ptr->operator[](idx); + break; + } + + default: + { + // we do not expect primitive values if there is still a + // reference token to process + return false; + } + } + } + + // no reference token left means we found a primitive value + return true; + } + + /*! + @brief split the string input to reference tokens + + @note This function is only called by the json_pointer constructor. + All exceptions below are documented there. + + @throw parse_error.107 if the pointer is not empty or begins with '/' + @throw parse_error.108 if character '~' is not followed by '0' or '1' + */ + static std::vector split(const std::string& reference_string) + { + std::vector result; + + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return result; + } + + // check if nonempty reference string begins with slash + if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) + { + JSON_THROW(detail::parse_error::create(107, 1, + "JSON pointer must be empty or begin with '/' - was: '" + + reference_string + "'")); + } + + // extract the reference tokens: + // - slash: position of the last read slash (or end of string) + // - start: position after the previous slash + for ( + // search for the first slash after the first character + std::size_t slash = reference_string.find_first_of('/', 1), + // set the beginning of the first reference token + start = 1; + // we can stop if start == 0 (if slash == std::string::npos) + start != 0; + // set the beginning of the next reference token + // (will eventually be 0 if slash == std::string::npos) + start = (slash == std::string::npos) ? 0 : slash + 1, + // find next slash + slash = reference_string.find_first_of('/', start)) + { + // use the text between the beginning of the reference token + // (start) and the last slash (slash). + auto reference_token = reference_string.substr(start, slash - start); + + // check reference tokens are properly escaped + for (std::size_t pos = reference_token.find_first_of('~'); + pos != std::string::npos; + pos = reference_token.find_first_of('~', pos + 1)) + { + JSON_ASSERT(reference_token[pos] == '~'); + + // ~ must be followed by 0 or 1 + if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 || + (reference_token[pos + 1] != '0' && + reference_token[pos + 1] != '1'))) + { + JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); + } + } + + // finally, store the reference token + unescape(reference_token); + result.push_back(reference_token); + } + + return result; + } + + /*! + @brief replace all occurrences of a substring by another string + + @param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t + @param[in] f the substring to replace with @a t + @param[in] t the string to replace @a f + + @pre The search string @a f must not be empty. **This precondition is + enforced with an assertion.** + + @since version 2.0.0 + */ + static void replace_substring(std::string& s, const std::string& f, + const std::string& t) + { + JSON_ASSERT(!f.empty()); + for (auto pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} + } + + /// escape "~" to "~0" and "/" to "~1" + static std::string escape(std::string s) + { + replace_substring(s, "~", "~0"); + replace_substring(s, "/", "~1"); + return s; + } + + /// unescape "~1" to tilde and "~0" to slash (order is important!) + static void unescape(std::string& s) + { + replace_substring(s, "~1", "/"); + replace_substring(s, "~0", "~"); + } + + /*! + @param[in] reference_string the reference string to the current value + @param[in] value the value to consider + @param[in,out] result the result object to insert values to + + @note Empty objects or arrays are flattened to `null`. + */ + static void flatten(const std::string& reference_string, + const BasicJsonType& value, + BasicJsonType& result) + { + switch (value.type()) + { + case detail::value_t::array: + { + if (value.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as reference string + for (std::size_t i = 0; i < value.m_value.array->size(); ++i) + { + flatten(reference_string + "/" + std::to_string(i), + value.m_value.array->operator[](i), result); + } + } + break; + } + + case detail::value_t::object: + { + if (value.m_value.object->empty()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_value.object) + { + flatten(reference_string + "/" + escape(element.first), element.second, result); + } + } + break; + } + + default: + { + // add primitive value with its reference string + result[reference_string] = value; + break; + } + } + } + + /*! + @param[in] value flattened JSON + + @return unflattened JSON + + @throw parse_error.109 if array index is not a number + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + @throw type_error.313 if value cannot be unflattened + */ + static BasicJsonType + unflatten(const BasicJsonType& value) + { + if (JSON_HEDLEY_UNLIKELY(!value.is_object())) + { + JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); + } + + BasicJsonType result; + + // iterate the JSON object values + for (const auto& element : *value.m_value.object) + { + if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) + { + JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); + } + + // assign value to reference pointed to by JSON pointer; Note that if + // the JSON pointer is "" (i.e., points to the whole value), function + // get_and_create returns a reference to result itself. An assignment + // will then create a primitive value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } + + /*! + @brief compares two JSON pointers for equality + + @param[in] lhs JSON pointer to compare + @param[in] rhs JSON pointer to compare + @return whether @a lhs is equal to @a rhs + + @complexity Linear in the length of the JSON pointer + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + */ + friend bool operator==(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return lhs.reference_tokens == rhs.reference_tokens; + } + + /*! + @brief compares two JSON pointers for inequality + + @param[in] lhs JSON pointer to compare + @param[in] rhs JSON pointer to compare + @return whether @a lhs is not equal @a rhs + + @complexity Linear in the length of the JSON pointer + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + */ + friend bool operator!=(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return !(lhs == rhs); + } + + /// the reference tokens + std::vector reference_tokens; +}; +} // namespace nlohmann + +// #include + + +#include +#include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template +class json_ref +{ + public: + using value_type = BasicJsonType; + + json_ref(value_type&& value) + : owned_value(std::move(value)) + , value_ref(&owned_value) + , is_rvalue(true) + {} + + json_ref(const value_type& value) + : value_ref(const_cast(&value)) + , is_rvalue(false) + {} + + json_ref(std::initializer_list init) + : owned_value(init) + , value_ref(&owned_value) + , is_rvalue(true) + {} + + template < + class... Args, + enable_if_t::value, int> = 0 > + json_ref(Args && ... args) + : owned_value(std::forward(args)...) + , value_ref(&owned_value) + , is_rvalue(true) + {} + + // class should be movable only + json_ref(json_ref&&) = default; + json_ref(const json_ref&) = delete; + json_ref& operator=(const json_ref&) = delete; + json_ref& operator=(json_ref&&) = delete; + ~json_ref() = default; + + value_type moved_or_copied() const + { + if (is_rvalue) + { + return std::move(*value_ref); + } + return *value_ref; + } + + value_type const& operator*() const + { + return *static_cast(value_ref); + } + + value_type const* operator->() const + { + return static_cast(value_ref); + } + + private: + mutable value_type owned_value = nullptr; + value_type* value_ref = nullptr; + const bool is_rvalue = true; +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + +// #include + +// #include + + +#include // reverse +#include // array +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // memcpy +#include // numeric_limits +#include // string +#include // isnan, isinf + +// #include + +// #include + +// #include + + +#include // copy +#include // size_t +#include // streamsize +#include // back_inserter +#include // shared_ptr, make_shared +#include // basic_ostream +#include // basic_string +#include // vector +// #include + + +namespace nlohmann +{ +namespace detail +{ +/// abstract output adapter interface +template struct output_adapter_protocol +{ + virtual void write_character(CharType c) = 0; + virtual void write_characters(const CharType* s, std::size_t length) = 0; + virtual ~output_adapter_protocol() = default; +}; + +/// a type to simplify interfaces +template +using output_adapter_t = std::shared_ptr>; + +/// output adapter for byte vectors +template +class output_vector_adapter : public output_adapter_protocol +{ + public: + explicit output_vector_adapter(std::vector& vec) noexcept + : v(vec) + {} + + void write_character(CharType c) override + { + v.push_back(c); + } + + JSON_HEDLEY_NON_NULL(2) + void write_characters(const CharType* s, std::size_t length) override + { + std::copy(s, s + length, std::back_inserter(v)); + } + + private: + std::vector& v; +}; + +/// output adapter for output streams +template +class output_stream_adapter : public output_adapter_protocol +{ + public: + explicit output_stream_adapter(std::basic_ostream& s) noexcept + : stream(s) + {} + + void write_character(CharType c) override + { + stream.put(c); + } + + JSON_HEDLEY_NON_NULL(2) + void write_characters(const CharType* s, std::size_t length) override + { + stream.write(s, static_cast(length)); + } + + private: + std::basic_ostream& stream; +}; + +/// output adapter for basic_string +template> +class output_string_adapter : public output_adapter_protocol +{ + public: + explicit output_string_adapter(StringType& s) noexcept + : str(s) + {} + + void write_character(CharType c) override + { + str.push_back(c); + } + + JSON_HEDLEY_NON_NULL(2) + void write_characters(const CharType* s, std::size_t length) override + { + str.append(s, length); + } + + private: + StringType& str; +}; + +template> +class output_adapter +{ + public: + output_adapter(std::vector& vec) + : oa(std::make_shared>(vec)) {} + + output_adapter(std::basic_ostream& s) + : oa(std::make_shared>(s)) {} + + output_adapter(StringType& s) + : oa(std::make_shared>(s)) {} + + operator output_adapter_t() + { + return oa; + } + + private: + output_adapter_t oa = nullptr; +}; +} // namespace detail +} // namespace nlohmann + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// binary writer // +/////////////////// + +/*! +@brief serialization to CBOR and MessagePack values +*/ +template +class binary_writer +{ + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using number_float_t = typename BasicJsonType::number_float_t; + + public: + /*! + @brief create a binary writer + + @param[in] adapter output adapter to write to + */ + explicit binary_writer(output_adapter_t adapter) : oa(adapter) + { + JSON_ASSERT(oa); + } + + /*! + @param[in] j JSON value to serialize + @pre j.type() == value_t::object + */ + void write_bson(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::object: + { + write_bson_object(*j.m_value.object); + break; + } + + default: + { + JSON_THROW(type_error::create(317, "to serialize to BSON, top-level type must be object, but is " + std::string(j.type_name()))); + } + } + } + + /*! + @param[in] j JSON value to serialize + */ + void write_cbor(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: + { + oa->write_character(to_char_type(0xF6)); + break; + } + + case value_t::boolean: + { + oa->write_character(j.m_value.boolean + ? to_char_type(0xF5) + : to_char_type(0xF4)); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // CBOR does not differentiate between positive signed + // integers and unsigned integers. Therefore, we used the + // code from the value_t::number_unsigned case here. + if (j.m_value.number_integer <= 0x17) + { + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x18)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x19)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x1A)); + write_number(static_cast(j.m_value.number_integer)); + } + else + { + oa->write_character(to_char_type(0x1B)); + write_number(static_cast(j.m_value.number_integer)); + } + } + else + { + // The conversions below encode the sign in the first + // byte, and the value is converted to a positive number. + const auto positive_number = -1 - j.m_value.number_integer; + if (j.m_value.number_integer >= -24) + { + write_number(static_cast(0x20 + positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x38)); + write_number(static_cast(positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x39)); + write_number(static_cast(positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x3A)); + write_number(static_cast(positive_number)); + } + else + { + oa->write_character(to_char_type(0x3B)); + write_number(static_cast(positive_number)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= 0x17) + { + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x18)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x19)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x1A)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else + { + oa->write_character(to_char_type(0x1B)); + write_number(static_cast(j.m_value.number_unsigned)); + } + break; + } + + case value_t::number_float: + { + if (std::isnan(j.m_value.number_float)) + { + // NaN is 0xf97e00 in CBOR + oa->write_character(to_char_type(0xF9)); + oa->write_character(to_char_type(0x7E)); + oa->write_character(to_char_type(0x00)); + } + else if (std::isinf(j.m_value.number_float)) + { + // Infinity is 0xf97c00, -Infinity is 0xf9fc00 + oa->write_character(to_char_type(0xf9)); + oa->write_character(j.m_value.number_float > 0 ? to_char_type(0x7C) : to_char_type(0xFC)); + oa->write_character(to_char_type(0x00)); + } + else + { + write_compact_float(j.m_value.number_float, detail::input_format_t::cbor); + } + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_value.string->size(); + if (N <= 0x17) + { + write_number(static_cast(0x60 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x78)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x79)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x7A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x7B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write the string + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_value.array->size(); + if (N <= 0x17) + { + write_number(static_cast(0x80 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x98)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x99)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x9A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x9B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_value.array) + { + write_cbor(el); + } + break; + } + + case value_t::binary: + { + if (j.m_value.binary->has_subtype()) + { + write_number(static_cast(0xd8)); + write_number(j.m_value.binary->subtype()); + } + + // step 1: write control byte and the binary array size + const auto N = j.m_value.binary->size(); + if (N <= 0x17) + { + write_number(static_cast(0x40 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x58)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x59)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x5A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x5B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + oa->write_characters( + reinterpret_cast(j.m_value.binary->data()), + N); + + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_value.object->size(); + if (N <= 0x17) + { + write_number(static_cast(0xA0 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0xB8)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0xB9)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0xBA)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0xBB)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_value.object) + { + write_cbor(el.first); + write_cbor(el.second); + } + break; + } + + default: + break; + } + } + + /*! + @param[in] j JSON value to serialize + */ + void write_msgpack(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: // nil + { + oa->write_character(to_char_type(0xC0)); + break; + } + + case value_t::boolean: // true and false + { + oa->write_character(j.m_value.boolean + ? to_char_type(0xC3) + : to_char_type(0xC2)); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // MessagePack does not differentiate between positive + // signed integers and unsigned integers. Therefore, we used + // the code from the value_t::number_unsigned case here. + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 8 + oa->write_character(to_char_type(0xCC)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 16 + oa->write_character(to_char_type(0xCD)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 32 + oa->write_character(to_char_type(0xCE)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 64 + oa->write_character(to_char_type(0xCF)); + write_number(static_cast(j.m_value.number_integer)); + } + } + else + { + if (j.m_value.number_integer >= -32) + { + // negative fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 8 + oa->write_character(to_char_type(0xD0)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 16 + oa->write_character(to_char_type(0xD1)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 32 + oa->write_character(to_char_type(0xD2)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 64 + oa->write_character(to_char_type(0xD3)); + write_number(static_cast(j.m_value.number_integer)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 8 + oa->write_character(to_char_type(0xCC)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 16 + oa->write_character(to_char_type(0xCD)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 32 + oa->write_character(to_char_type(0xCE)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 64 + oa->write_character(to_char_type(0xCF)); + write_number(static_cast(j.m_value.number_integer)); + } + break; + } + + case value_t::number_float: + { + write_compact_float(j.m_value.number_float, detail::input_format_t::msgpack); + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_value.string->size(); + if (N <= 31) + { + // fixstr + write_number(static_cast(0xA0 | N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 8 + oa->write_character(to_char_type(0xD9)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 16 + oa->write_character(to_char_type(0xDA)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 32 + oa->write_character(to_char_type(0xDB)); + write_number(static_cast(N)); + } + + // step 2: write the string + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_value.array->size(); + if (N <= 15) + { + // fixarray + write_number(static_cast(0x90 | N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // array 16 + oa->write_character(to_char_type(0xDC)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // array 32 + oa->write_character(to_char_type(0xDD)); + write_number(static_cast(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_value.array) + { + write_msgpack(el); + } + break; + } + + case value_t::binary: + { + // step 0: determine if the binary type has a set subtype to + // determine whether or not to use the ext or fixext types + const bool use_ext = j.m_value.binary->has_subtype(); + + // step 1: write control byte and the byte string length + const auto N = j.m_value.binary->size(); + if (N <= (std::numeric_limits::max)()) + { + std::uint8_t output_type{}; + bool fixed = true; + if (use_ext) + { + switch (N) + { + case 1: + output_type = 0xD4; // fixext 1 + break; + case 2: + output_type = 0xD5; // fixext 2 + break; + case 4: + output_type = 0xD6; // fixext 4 + break; + case 8: + output_type = 0xD7; // fixext 8 + break; + case 16: + output_type = 0xD8; // fixext 16 + break; + default: + output_type = 0xC7; // ext 8 + fixed = false; + break; + } + + } + else + { + output_type = 0xC4; // bin 8 + fixed = false; + } + + oa->write_character(to_char_type(output_type)); + if (!fixed) + { + write_number(static_cast(N)); + } + } + else if (N <= (std::numeric_limits::max)()) + { + std::uint8_t output_type = use_ext + ? 0xC8 // ext 16 + : 0xC5; // bin 16 + + oa->write_character(to_char_type(output_type)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + std::uint8_t output_type = use_ext + ? 0xC9 // ext 32 + : 0xC6; // bin 32 + + oa->write_character(to_char_type(output_type)); + write_number(static_cast(N)); + } + + // step 1.5: if this is an ext type, write the subtype + if (use_ext) + { + write_number(static_cast(j.m_value.binary->subtype())); + } + + // step 2: write the byte string + oa->write_characters( + reinterpret_cast(j.m_value.binary->data()), + N); + + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_value.object->size(); + if (N <= 15) + { + // fixmap + write_number(static_cast(0x80 | (N & 0xF))); + } + else if (N <= (std::numeric_limits::max)()) + { + // map 16 + oa->write_character(to_char_type(0xDE)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // map 32 + oa->write_character(to_char_type(0xDF)); + write_number(static_cast(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_value.object) + { + write_msgpack(el.first); + write_msgpack(el.second); + } + break; + } + + default: + break; + } + } + + /*! + @param[in] j JSON value to serialize + @param[in] use_count whether to use '#' prefixes (optimized format) + @param[in] use_type whether to use '$' prefixes (optimized format) + @param[in] add_prefix whether prefixes need to be used for this value + */ + void write_ubjson(const BasicJsonType& j, const bool use_count, + const bool use_type, const bool add_prefix = true) + { + switch (j.type()) + { + case value_t::null: + { + if (add_prefix) + { + oa->write_character(to_char_type('Z')); + } + break; + } + + case value_t::boolean: + { + if (add_prefix) + { + oa->write_character(j.m_value.boolean + ? to_char_type('T') + : to_char_type('F')); + } + break; + } + + case value_t::number_integer: + { + write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix); + break; + } + + case value_t::number_unsigned: + { + write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix); + break; + } + + case value_t::number_float: + { + write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix); + break; + } + + case value_t::string: + { + if (add_prefix) + { + oa->write_character(to_char_type('S')); + } + write_number_with_ubjson_prefix(j.m_value.string->size(), true); + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + if (add_prefix) + { + oa->write_character(to_char_type('[')); + } + + bool prefix_required = true; + if (use_type && !j.m_value.array->empty()) + { + JSON_ASSERT(use_count); + const CharType first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin() + 1, j.end(), + [this, first_prefix](const BasicJsonType & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + oa->write_character(to_char_type('$')); + oa->write_character(first_prefix); + } + } + + if (use_count) + { + oa->write_character(to_char_type('#')); + write_number_with_ubjson_prefix(j.m_value.array->size(), true); + } + + for (const auto& el : *j.m_value.array) + { + write_ubjson(el, use_count, use_type, prefix_required); + } + + if (!use_count) + { + oa->write_character(to_char_type(']')); + } + + break; + } + + case value_t::binary: + { + if (add_prefix) + { + oa->write_character(to_char_type('[')); + } + + if (use_type && !j.m_value.binary->empty()) + { + JSON_ASSERT(use_count); + oa->write_character(to_char_type('$')); + oa->write_character('U'); + } + + if (use_count) + { + oa->write_character(to_char_type('#')); + write_number_with_ubjson_prefix(j.m_value.binary->size(), true); + } + + if (use_type) + { + oa->write_characters( + reinterpret_cast(j.m_value.binary->data()), + j.m_value.binary->size()); + } + else + { + for (size_t i = 0; i < j.m_value.binary->size(); ++i) + { + oa->write_character(to_char_type('U')); + oa->write_character(j.m_value.binary->data()[i]); + } + } + + if (!use_count) + { + oa->write_character(to_char_type(']')); + } + + break; + } + + case value_t::object: + { + if (add_prefix) + { + oa->write_character(to_char_type('{')); + } + + bool prefix_required = true; + if (use_type && !j.m_value.object->empty()) + { + JSON_ASSERT(use_count); + const CharType first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin(), j.end(), + [this, first_prefix](const BasicJsonType & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + oa->write_character(to_char_type('$')); + oa->write_character(first_prefix); + } + } + + if (use_count) + { + oa->write_character(to_char_type('#')); + write_number_with_ubjson_prefix(j.m_value.object->size(), true); + } + + for (const auto& el : *j.m_value.object) + { + write_number_with_ubjson_prefix(el.first.size(), true); + oa->write_characters( + reinterpret_cast(el.first.c_str()), + el.first.size()); + write_ubjson(el.second, use_count, use_type, prefix_required); + } + + if (!use_count) + { + oa->write_character(to_char_type('}')); + } + + break; + } + + default: + break; + } + } + + private: + ////////// + // BSON // + ////////// + + /*! + @return The size of a BSON document entry header, including the id marker + and the entry name size (and its null-terminator). + */ + static std::size_t calc_bson_entry_header_size(const string_t& name) + { + const auto it = name.find(static_cast(0)); + if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) + { + JSON_THROW(out_of_range::create(409, + "BSON key cannot contain code point U+0000 (at byte " + std::to_string(it) + ")")); + } + + return /*id*/ 1ul + name.size() + /*zero-terminator*/1u; + } + + /*! + @brief Writes the given @a element_type and @a name to the output adapter + */ + void write_bson_entry_header(const string_t& name, + const std::uint8_t element_type) + { + oa->write_character(to_char_type(element_type)); // boolean + oa->write_characters( + reinterpret_cast(name.c_str()), + name.size() + 1u); + } + + /*! + @brief Writes a BSON element with key @a name and boolean value @a value + */ + void write_bson_boolean(const string_t& name, + const bool value) + { + write_bson_entry_header(name, 0x08); + oa->write_character(value ? to_char_type(0x01) : to_char_type(0x00)); + } + + /*! + @brief Writes a BSON element with key @a name and double value @a value + */ + void write_bson_double(const string_t& name, + const double value) + { + write_bson_entry_header(name, 0x01); + write_number(value); + } + + /*! + @return The size of the BSON-encoded string in @a value + */ + static std::size_t calc_bson_string_size(const string_t& value) + { + return sizeof(std::int32_t) + value.size() + 1ul; + } + + /*! + @brief Writes a BSON element with key @a name and string value @a value + */ + void write_bson_string(const string_t& name, + const string_t& value) + { + write_bson_entry_header(name, 0x02); + + write_number(static_cast(value.size() + 1ul)); + oa->write_characters( + reinterpret_cast(value.c_str()), + value.size() + 1); + } + + /*! + @brief Writes a BSON element with key @a name and null value + */ + void write_bson_null(const string_t& name) + { + write_bson_entry_header(name, 0x0A); + } + + /*! + @return The size of the BSON-encoded integer @a value + */ + static std::size_t calc_bson_integer_size(const std::int64_t value) + { + return (std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)() + ? sizeof(std::int32_t) + : sizeof(std::int64_t); + } + + /*! + @brief Writes a BSON element with key @a name and integer @a value + */ + void write_bson_integer(const string_t& name, + const std::int64_t value) + { + if ((std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)()) + { + write_bson_entry_header(name, 0x10); // int32 + write_number(static_cast(value)); + } + else + { + write_bson_entry_header(name, 0x12); // int64 + write_number(static_cast(value)); + } + } + + /*! + @return The size of the BSON-encoded unsigned integer in @a j + */ + static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value) noexcept + { + return (value <= static_cast((std::numeric_limits::max)())) + ? sizeof(std::int32_t) + : sizeof(std::int64_t); + } + + /*! + @brief Writes a BSON element with key @a name and unsigned @a value + */ + void write_bson_unsigned(const string_t& name, + const std::uint64_t value) + { + if (value <= static_cast((std::numeric_limits::max)())) + { + write_bson_entry_header(name, 0x10 /* int32 */); + write_number(static_cast(value)); + } + else if (value <= static_cast((std::numeric_limits::max)())) + { + write_bson_entry_header(name, 0x12 /* int64 */); + write_number(static_cast(value)); + } + else + { + JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(value) + " cannot be represented by BSON as it does not fit int64")); + } + } + + /*! + @brief Writes a BSON element with key @a name and object @a value + */ + void write_bson_object_entry(const string_t& name, + const typename BasicJsonType::object_t& value) + { + write_bson_entry_header(name, 0x03); // object + write_bson_object(value); + } + + /*! + @return The size of the BSON-encoded array @a value + */ + static std::size_t calc_bson_array_size(const typename BasicJsonType::array_t& value) + { + std::size_t array_index = 0ul; + + const std::size_t embedded_document_size = std::accumulate(std::begin(value), std::end(value), std::size_t(0), [&array_index](std::size_t result, const typename BasicJsonType::array_t::value_type & el) + { + return result + calc_bson_element_size(std::to_string(array_index++), el); + }); + + return sizeof(std::int32_t) + embedded_document_size + 1ul; + } + + /*! + @return The size of the BSON-encoded binary array @a value + */ + static std::size_t calc_bson_binary_size(const typename BasicJsonType::binary_t& value) + { + return sizeof(std::int32_t) + value.size() + 1ul; + } + + /*! + @brief Writes a BSON element with key @a name and array @a value + */ + void write_bson_array(const string_t& name, + const typename BasicJsonType::array_t& value) + { + write_bson_entry_header(name, 0x04); // array + write_number(static_cast(calc_bson_array_size(value))); + + std::size_t array_index = 0ul; + + for (const auto& el : value) + { + write_bson_element(std::to_string(array_index++), el); + } + + oa->write_character(to_char_type(0x00)); + } + + /*! + @brief Writes a BSON element with key @a name and binary value @a value + */ + void write_bson_binary(const string_t& name, + const binary_t& value) + { + write_bson_entry_header(name, 0x05); + + write_number(static_cast(value.size())); + write_number(value.has_subtype() ? value.subtype() : std::uint8_t(0x00)); + + oa->write_characters(reinterpret_cast(value.data()), value.size()); + } + + /*! + @brief Calculates the size necessary to serialize the JSON value @a j with its @a name + @return The calculated size for the BSON document entry for @a j with the given @a name. + */ + static std::size_t calc_bson_element_size(const string_t& name, + const BasicJsonType& j) + { + const auto header_size = calc_bson_entry_header_size(name); + switch (j.type()) + { + case value_t::object: + return header_size + calc_bson_object_size(*j.m_value.object); + + case value_t::array: + return header_size + calc_bson_array_size(*j.m_value.array); + + case value_t::binary: + return header_size + calc_bson_binary_size(*j.m_value.binary); + + case value_t::boolean: + return header_size + 1ul; + + case value_t::number_float: + return header_size + 8ul; + + case value_t::number_integer: + return header_size + calc_bson_integer_size(j.m_value.number_integer); + + case value_t::number_unsigned: + return header_size + calc_bson_unsigned_size(j.m_value.number_unsigned); + + case value_t::string: + return header_size + calc_bson_string_size(*j.m_value.string); + + case value_t::null: + return header_size + 0ul; + + // LCOV_EXCL_START + default: + JSON_ASSERT(false); + return 0ul; + // LCOV_EXCL_STOP + } + } + + /*! + @brief Serializes the JSON value @a j to BSON and associates it with the + key @a name. + @param name The name to associate with the JSON entity @a j within the + current BSON document + @return The size of the BSON entry + */ + void write_bson_element(const string_t& name, + const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::object: + return write_bson_object_entry(name, *j.m_value.object); + + case value_t::array: + return write_bson_array(name, *j.m_value.array); + + case value_t::binary: + return write_bson_binary(name, *j.m_value.binary); + + case value_t::boolean: + return write_bson_boolean(name, j.m_value.boolean); + + case value_t::number_float: + return write_bson_double(name, j.m_value.number_float); + + case value_t::number_integer: + return write_bson_integer(name, j.m_value.number_integer); + + case value_t::number_unsigned: + return write_bson_unsigned(name, j.m_value.number_unsigned); + + case value_t::string: + return write_bson_string(name, *j.m_value.string); + + case value_t::null: + return write_bson_null(name); + + // LCOV_EXCL_START + default: + JSON_ASSERT(false); + return; + // LCOV_EXCL_STOP + } + } + + /*! + @brief Calculates the size of the BSON serialization of the given + JSON-object @a j. + @param[in] j JSON value to serialize + @pre j.type() == value_t::object + */ + static std::size_t calc_bson_object_size(const typename BasicJsonType::object_t& value) + { + std::size_t document_size = std::accumulate(value.begin(), value.end(), std::size_t(0), + [](size_t result, const typename BasicJsonType::object_t::value_type & el) + { + return result += calc_bson_element_size(el.first, el.second); + }); + + return sizeof(std::int32_t) + document_size + 1ul; + } + + /*! + @param[in] j JSON value to serialize + @pre j.type() == value_t::object + */ + void write_bson_object(const typename BasicJsonType::object_t& value) + { + write_number(static_cast(calc_bson_object_size(value))); + + for (const auto& el : value) + { + write_bson_element(el.first, el.second); + } + + oa->write_character(to_char_type(0x00)); + } + + ////////// + // CBOR // + ////////// + + static constexpr CharType get_cbor_float_prefix(float /*unused*/) + { + return to_char_type(0xFA); // Single-Precision Float + } + + static constexpr CharType get_cbor_float_prefix(double /*unused*/) + { + return to_char_type(0xFB); // Double-Precision Float + } + + ///////////// + // MsgPack // + ///////////// + + static constexpr CharType get_msgpack_float_prefix(float /*unused*/) + { + return to_char_type(0xCA); // float 32 + } + + static constexpr CharType get_msgpack_float_prefix(double /*unused*/) + { + return to_char_type(0xCB); // float 64 + } + + //////////// + // UBJSON // + //////////// + + // UBJSON: write number (floating point) + template::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if (add_prefix) + { + oa->write_character(get_ubjson_float_prefix(n)); + } + write_number(n); + } + + // UBJSON: write number (unsigned integer) + template::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('i')); // int8 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('U')); // uint8 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('I')); // int16 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('l')); // int32 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('L')); // int64 + } + write_number(static_cast(n)); + } + else + { + if (add_prefix) + { + oa->write_character(to_char_type('H')); // high-precision number + } + + const auto number = BasicJsonType(n).dump(); + write_number_with_ubjson_prefix(number.size(), true); + for (std::size_t i = 0; i < number.size(); ++i) + { + oa->write_character(to_char_type(static_cast(number[i]))); + } + } + } + + // UBJSON: write number (signed integer) + template < typename NumberType, typename std::enable_if < + std::is_signed::value&& + !std::is_floating_point::value, int >::type = 0 > + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('i')); // int8 + } + write_number(static_cast(n)); + } + else if (static_cast((std::numeric_limits::min)()) <= n && n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('U')); // uint8 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('I')); // int16 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('l')); // int32 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('L')); // int64 + } + write_number(static_cast(n)); + } + // LCOV_EXCL_START + else + { + if (add_prefix) + { + oa->write_character(to_char_type('H')); // high-precision number + } + + const auto number = BasicJsonType(n).dump(); + write_number_with_ubjson_prefix(number.size(), true); + for (std::size_t i = 0; i < number.size(); ++i) + { + oa->write_character(to_char_type(static_cast(number[i]))); + } + } + // LCOV_EXCL_STOP + } + + /*! + @brief determine the type prefix of container values + */ + CharType ubjson_prefix(const BasicJsonType& j) const noexcept + { + switch (j.type()) + { + case value_t::null: + return 'Z'; + + case value_t::boolean: + return j.m_value.boolean ? 'T' : 'F'; + + case value_t::number_integer: + { + if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'i'; + } + if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'U'; + } + if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'I'; + } + if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'l'; + } + if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'L'; + } + // anything else is treated as high-precision number + return 'H'; // LCOV_EXCL_LINE + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'i'; + } + if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'U'; + } + if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'I'; + } + if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'l'; + } + if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'L'; + } + // anything else is treated as high-precision number + return 'H'; // LCOV_EXCL_LINE + } + + case value_t::number_float: + return get_ubjson_float_prefix(j.m_value.number_float); + + case value_t::string: + return 'S'; + + case value_t::array: // fallthrough + case value_t::binary: + return '['; + + case value_t::object: + return '{'; + + default: // discarded values + return 'N'; + } + } + + static constexpr CharType get_ubjson_float_prefix(float /*unused*/) + { + return 'd'; // float 32 + } + + static constexpr CharType get_ubjson_float_prefix(double /*unused*/) + { + return 'D'; // float 64 + } + + /////////////////////// + // Utility functions // + /////////////////////// + + /* + @brief write a number to output input + @param[in] n number of type @a NumberType + @tparam NumberType the type of the number + @tparam OutputIsLittleEndian Set to true if output data is + required to be little endian + + @note This function needs to respect the system's endianess, because bytes + in CBOR, MessagePack, and UBJSON are stored in network order (big + endian) and therefore need reordering on little endian systems. + */ + template + void write_number(const NumberType n) + { + // step 1: write number to array of length NumberType + std::array vec; + std::memcpy(vec.data(), &n, sizeof(NumberType)); + + // step 2: write array to output (with possible reordering) + if (is_little_endian != OutputIsLittleEndian) + { + // reverse byte order prior to conversion if necessary + std::reverse(vec.begin(), vec.end()); + } + + oa->write_characters(vec.data(), sizeof(NumberType)); + } + + void write_compact_float(const number_float_t n, detail::input_format_t format) + { + if (static_cast(n) >= static_cast(std::numeric_limits::lowest()) && + static_cast(n) <= static_cast((std::numeric_limits::max)()) && + static_cast(static_cast(n)) == static_cast(n)) + { + oa->write_character(format == detail::input_format_t::cbor + ? get_cbor_float_prefix(static_cast(n)) + : get_msgpack_float_prefix(static_cast(n))); + write_number(static_cast(n)); + } + else + { + oa->write_character(format == detail::input_format_t::cbor + ? get_cbor_float_prefix(n) + : get_msgpack_float_prefix(n)); + write_number(n); + } + } + + public: + // The following to_char_type functions are implement the conversion + // between uint8_t and CharType. In case CharType is not unsigned, + // such a conversion is required to allow values greater than 128. + // See for a discussion. + template < typename C = CharType, + enable_if_t < std::is_signed::value && std::is_signed::value > * = nullptr > + static constexpr CharType to_char_type(std::uint8_t x) noexcept + { + return *reinterpret_cast(&x); + } + + template < typename C = CharType, + enable_if_t < std::is_signed::value && std::is_unsigned::value > * = nullptr > + static CharType to_char_type(std::uint8_t x) noexcept + { + static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t"); + static_assert(std::is_trivial::value, "CharType must be trivial"); + CharType result; + std::memcpy(&result, &x, sizeof(x)); + return result; + } + + template::value>* = nullptr> + static constexpr CharType to_char_type(std::uint8_t x) noexcept + { + return x; + } + + template < typename InputCharType, typename C = CharType, + enable_if_t < + std::is_signed::value && + std::is_signed::value && + std::is_same::type>::value + > * = nullptr > + static constexpr CharType to_char_type(InputCharType x) noexcept + { + return x; + } + + private: + /// whether we can assume little endianess + const bool is_little_endian = little_endianess(); + + /// the output + output_adapter_t oa = nullptr; +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + + +#include // reverse, remove, fill, find, none_of +#include // array +#include // localeconv, lconv +#include // labs, isfinite, isnan, signbit +#include // size_t, ptrdiff_t +#include // uint8_t +#include // snprintf +#include // numeric_limits +#include // string, char_traits +#include // is_same +#include // move + +// #include + + +#include // array +#include // signbit, isfinite +#include // intN_t, uintN_t +#include // memcpy, memmove +#include // numeric_limits +#include // conditional + +// #include + + +namespace nlohmann +{ +namespace detail +{ + +/*! +@brief implements the Grisu2 algorithm for binary to decimal floating-point +conversion. + +This implementation is a slightly modified version of the reference +implementation which may be obtained from +http://florian.loitsch.com/publications (bench.tar.gz). + +The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch. + +For a detailed description of the algorithm see: + +[1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with + Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming + Language Design and Implementation, PLDI 2010 +[2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", + Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language + Design and Implementation, PLDI 1996 +*/ +namespace dtoa_impl +{ + +template +Target reinterpret_bits(const Source source) +{ + static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); + + Target target; + std::memcpy(&target, &source, sizeof(Source)); + return target; +} + +struct diyfp // f * 2^e +{ + static constexpr int kPrecision = 64; // = q + + std::uint64_t f = 0; + int e = 0; + + constexpr diyfp(std::uint64_t f_, int e_) noexcept : f(f_), e(e_) {} + + /*! + @brief returns x - y + @pre x.e == y.e and x.f >= y.f + */ + static diyfp sub(const diyfp& x, const diyfp& y) noexcept + { + JSON_ASSERT(x.e == y.e); + JSON_ASSERT(x.f >= y.f); + + return {x.f - y.f, x.e}; + } + + /*! + @brief returns x * y + @note The result is rounded. (Only the upper q bits are returned.) + */ + static diyfp mul(const diyfp& x, const diyfp& y) noexcept + { + static_assert(kPrecision == 64, "internal error"); + + // Computes: + // f = round((x.f * y.f) / 2^q) + // e = x.e + y.e + q + + // Emulate the 64-bit * 64-bit multiplication: + // + // p = u * v + // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) + // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi ) + // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 ) + // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) + // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3) + // = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) + // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) + // + // (Since Q might be larger than 2^32 - 1) + // + // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) + // + // (Q_hi + H does not overflow a 64-bit int) + // + // = p_lo + 2^64 p_hi + + const std::uint64_t u_lo = x.f & 0xFFFFFFFFu; + const std::uint64_t u_hi = x.f >> 32u; + const std::uint64_t v_lo = y.f & 0xFFFFFFFFu; + const std::uint64_t v_hi = y.f >> 32u; + + const std::uint64_t p0 = u_lo * v_lo; + const std::uint64_t p1 = u_lo * v_hi; + const std::uint64_t p2 = u_hi * v_lo; + const std::uint64_t p3 = u_hi * v_hi; + + const std::uint64_t p0_hi = p0 >> 32u; + const std::uint64_t p1_lo = p1 & 0xFFFFFFFFu; + const std::uint64_t p1_hi = p1 >> 32u; + const std::uint64_t p2_lo = p2 & 0xFFFFFFFFu; + const std::uint64_t p2_hi = p2 >> 32u; + + std::uint64_t Q = p0_hi + p1_lo + p2_lo; + + // The full product might now be computed as + // + // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) + // p_lo = p0_lo + (Q << 32) + // + // But in this particular case here, the full p_lo is not required. + // Effectively we only need to add the highest bit in p_lo to p_hi (and + // Q_hi + 1 does not overflow). + + Q += std::uint64_t{1} << (64u - 32u - 1u); // round, ties up + + const std::uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32u); + + return {h, x.e + y.e + 64}; + } + + /*! + @brief normalize x such that the significand is >= 2^(q-1) + @pre x.f != 0 + */ + static diyfp normalize(diyfp x) noexcept + { + JSON_ASSERT(x.f != 0); + + while ((x.f >> 63u) == 0) + { + x.f <<= 1u; + x.e--; + } + + return x; + } + + /*! + @brief normalize x such that the result has the exponent E + @pre e >= x.e and the upper e - x.e bits of x.f must be zero. + */ + static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept + { + const int delta = x.e - target_exponent; + + JSON_ASSERT(delta >= 0); + JSON_ASSERT(((x.f << delta) >> delta) == x.f); + + return {x.f << delta, target_exponent}; + } +}; + +struct boundaries +{ + diyfp w; + diyfp minus; + diyfp plus; +}; + +/*! +Compute the (normalized) diyfp representing the input number 'value' and its +boundaries. + +@pre value must be finite and positive +*/ +template +boundaries compute_boundaries(FloatType value) +{ + JSON_ASSERT(std::isfinite(value)); + JSON_ASSERT(value > 0); + + // Convert the IEEE representation into a diyfp. + // + // If v is denormal: + // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) + // If v is normalized: + // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) + + static_assert(std::numeric_limits::is_iec559, + "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); + + constexpr int kPrecision = std::numeric_limits::digits; // = p (includes the hidden bit) + constexpr int kBias = std::numeric_limits::max_exponent - 1 + (kPrecision - 1); + constexpr int kMinExp = 1 - kBias; + constexpr std::uint64_t kHiddenBit = std::uint64_t{1} << (kPrecision - 1); // = 2^(p-1) + + using bits_type = typename std::conditional::type; + + const std::uint64_t bits = reinterpret_bits(value); + const std::uint64_t E = bits >> (kPrecision - 1); + const std::uint64_t F = bits & (kHiddenBit - 1); + + const bool is_denormal = E == 0; + const diyfp v = is_denormal + ? diyfp(F, kMinExp) + : diyfp(F + kHiddenBit, static_cast(E) - kBias); + + // Compute the boundaries m- and m+ of the floating-point value + // v = f * 2^e. + // + // Determine v- and v+, the floating-point predecessor and successor if v, + // respectively. + // + // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) + // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) + // + // v+ = v + 2^e + // + // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ + // between m- and m+ round to v, regardless of how the input rounding + // algorithm breaks ties. + // + // ---+-------------+-------------+-------------+-------------+--- (A) + // v- m- v m+ v+ + // + // -----------------+------+------+-------------+-------------+--- (B) + // v- m- v m+ v+ + + const bool lower_boundary_is_closer = F == 0 && E > 1; + const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); + const diyfp m_minus = lower_boundary_is_closer + ? diyfp(4 * v.f - 1, v.e - 2) // (B) + : diyfp(2 * v.f - 1, v.e - 1); // (A) + + // Determine the normalized w+ = m+. + const diyfp w_plus = diyfp::normalize(m_plus); + + // Determine w- = m- such that e_(w-) = e_(w+). + const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); + + return {diyfp::normalize(v), w_minus, w_plus}; +} + +// Given normalized diyfp w, Grisu needs to find a (normalized) cached +// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies +// within a certain range [alpha, gamma] (Definition 3.2 from [1]) +// +// alpha <= e = e_c + e_w + q <= gamma +// +// or +// +// f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q +// <= f_c * f_w * 2^gamma +// +// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies +// +// 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma +// +// or +// +// 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) +// +// The choice of (alpha,gamma) determines the size of the table and the form of +// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well +// in practice: +// +// The idea is to cut the number c * w = f * 2^e into two parts, which can be +// processed independently: An integral part p1, and a fractional part p2: +// +// f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e +// = (f div 2^-e) + (f mod 2^-e) * 2^e +// = p1 + p2 * 2^e +// +// The conversion of p1 into decimal form requires a series of divisions and +// modulos by (a power of) 10. These operations are faster for 32-bit than for +// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be +// achieved by choosing +// +// -e >= 32 or e <= -32 := gamma +// +// In order to convert the fractional part +// +// p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... +// +// into decimal form, the fraction is repeatedly multiplied by 10 and the digits +// d[-i] are extracted in order: +// +// (10 * p2) div 2^-e = d[-1] +// (10 * p2) mod 2^-e = d[-2] / 10^1 + ... +// +// The multiplication by 10 must not overflow. It is sufficient to choose +// +// 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. +// +// Since p2 = f mod 2^-e < 2^-e, +// +// -e <= 60 or e >= -60 := alpha + +constexpr int kAlpha = -60; +constexpr int kGamma = -32; + +struct cached_power // c = f * 2^e ~= 10^k +{ + std::uint64_t f; + int e; + int k; +}; + +/*! +For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached +power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c +satisfies (Definition 3.2 from [1]) + + alpha <= e_c + e + q <= gamma. +*/ +inline cached_power get_cached_power_for_binary_exponent(int e) +{ + // Now + // + // alpha <= e_c + e + q <= gamma (1) + // ==> f_c * 2^alpha <= c * 2^e * 2^q + // + // and since the c's are normalized, 2^(q-1) <= f_c, + // + // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) + // ==> 2^(alpha - e - 1) <= c + // + // If c were an exact power of ten, i.e. c = 10^k, one may determine k as + // + // k = ceil( log_10( 2^(alpha - e - 1) ) ) + // = ceil( (alpha - e - 1) * log_10(2) ) + // + // From the paper: + // "In theory the result of the procedure could be wrong since c is rounded, + // and the computation itself is approximated [...]. In practice, however, + // this simple function is sufficient." + // + // For IEEE double precision floating-point numbers converted into + // normalized diyfp's w = f * 2^e, with q = 64, + // + // e >= -1022 (min IEEE exponent) + // -52 (p - 1) + // -52 (p - 1, possibly normalize denormal IEEE numbers) + // -11 (normalize the diyfp) + // = -1137 + // + // and + // + // e <= +1023 (max IEEE exponent) + // -52 (p - 1) + // -11 (normalize the diyfp) + // = 960 + // + // This binary exponent range [-1137,960] results in a decimal exponent + // range [-307,324]. One does not need to store a cached power for each + // k in this range. For each such k it suffices to find a cached power + // such that the exponent of the product lies in [alpha,gamma]. + // This implies that the difference of the decimal exponents of adjacent + // table entries must be less than or equal to + // + // floor( (gamma - alpha) * log_10(2) ) = 8. + // + // (A smaller distance gamma-alpha would require a larger table.) + + // NB: + // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. + + constexpr int kCachedPowersMinDecExp = -300; + constexpr int kCachedPowersDecStep = 8; + + static constexpr std::array kCachedPowers = + { + { + { 0xAB70FE17C79AC6CA, -1060, -300 }, + { 0xFF77B1FCBEBCDC4F, -1034, -292 }, + { 0xBE5691EF416BD60C, -1007, -284 }, + { 0x8DD01FAD907FFC3C, -980, -276 }, + { 0xD3515C2831559A83, -954, -268 }, + { 0x9D71AC8FADA6C9B5, -927, -260 }, + { 0xEA9C227723EE8BCB, -901, -252 }, + { 0xAECC49914078536D, -874, -244 }, + { 0x823C12795DB6CE57, -847, -236 }, + { 0xC21094364DFB5637, -821, -228 }, + { 0x9096EA6F3848984F, -794, -220 }, + { 0xD77485CB25823AC7, -768, -212 }, + { 0xA086CFCD97BF97F4, -741, -204 }, + { 0xEF340A98172AACE5, -715, -196 }, + { 0xB23867FB2A35B28E, -688, -188 }, + { 0x84C8D4DFD2C63F3B, -661, -180 }, + { 0xC5DD44271AD3CDBA, -635, -172 }, + { 0x936B9FCEBB25C996, -608, -164 }, + { 0xDBAC6C247D62A584, -582, -156 }, + { 0xA3AB66580D5FDAF6, -555, -148 }, + { 0xF3E2F893DEC3F126, -529, -140 }, + { 0xB5B5ADA8AAFF80B8, -502, -132 }, + { 0x87625F056C7C4A8B, -475, -124 }, + { 0xC9BCFF6034C13053, -449, -116 }, + { 0x964E858C91BA2655, -422, -108 }, + { 0xDFF9772470297EBD, -396, -100 }, + { 0xA6DFBD9FB8E5B88F, -369, -92 }, + { 0xF8A95FCF88747D94, -343, -84 }, + { 0xB94470938FA89BCF, -316, -76 }, + { 0x8A08F0F8BF0F156B, -289, -68 }, + { 0xCDB02555653131B6, -263, -60 }, + { 0x993FE2C6D07B7FAC, -236, -52 }, + { 0xE45C10C42A2B3B06, -210, -44 }, + { 0xAA242499697392D3, -183, -36 }, + { 0xFD87B5F28300CA0E, -157, -28 }, + { 0xBCE5086492111AEB, -130, -20 }, + { 0x8CBCCC096F5088CC, -103, -12 }, + { 0xD1B71758E219652C, -77, -4 }, + { 0x9C40000000000000, -50, 4 }, + { 0xE8D4A51000000000, -24, 12 }, + { 0xAD78EBC5AC620000, 3, 20 }, + { 0x813F3978F8940984, 30, 28 }, + { 0xC097CE7BC90715B3, 56, 36 }, + { 0x8F7E32CE7BEA5C70, 83, 44 }, + { 0xD5D238A4ABE98068, 109, 52 }, + { 0x9F4F2726179A2245, 136, 60 }, + { 0xED63A231D4C4FB27, 162, 68 }, + { 0xB0DE65388CC8ADA8, 189, 76 }, + { 0x83C7088E1AAB65DB, 216, 84 }, + { 0xC45D1DF942711D9A, 242, 92 }, + { 0x924D692CA61BE758, 269, 100 }, + { 0xDA01EE641A708DEA, 295, 108 }, + { 0xA26DA3999AEF774A, 322, 116 }, + { 0xF209787BB47D6B85, 348, 124 }, + { 0xB454E4A179DD1877, 375, 132 }, + { 0x865B86925B9BC5C2, 402, 140 }, + { 0xC83553C5C8965D3D, 428, 148 }, + { 0x952AB45CFA97A0B3, 455, 156 }, + { 0xDE469FBD99A05FE3, 481, 164 }, + { 0xA59BC234DB398C25, 508, 172 }, + { 0xF6C69A72A3989F5C, 534, 180 }, + { 0xB7DCBF5354E9BECE, 561, 188 }, + { 0x88FCF317F22241E2, 588, 196 }, + { 0xCC20CE9BD35C78A5, 614, 204 }, + { 0x98165AF37B2153DF, 641, 212 }, + { 0xE2A0B5DC971F303A, 667, 220 }, + { 0xA8D9D1535CE3B396, 694, 228 }, + { 0xFB9B7CD9A4A7443C, 720, 236 }, + { 0xBB764C4CA7A44410, 747, 244 }, + { 0x8BAB8EEFB6409C1A, 774, 252 }, + { 0xD01FEF10A657842C, 800, 260 }, + { 0x9B10A4E5E9913129, 827, 268 }, + { 0xE7109BFBA19C0C9D, 853, 276 }, + { 0xAC2820D9623BF429, 880, 284 }, + { 0x80444B5E7AA7CF85, 907, 292 }, + { 0xBF21E44003ACDD2D, 933, 300 }, + { 0x8E679C2F5E44FF8F, 960, 308 }, + { 0xD433179D9C8CB841, 986, 316 }, + { 0x9E19DB92B4E31BA9, 1013, 324 }, + } + }; + + // This computation gives exactly the same results for k as + // k = ceil((kAlpha - e - 1) * 0.30102999566398114) + // for |e| <= 1500, but doesn't require floating-point operations. + // NB: log_10(2) ~= 78913 / 2^18 + JSON_ASSERT(e >= -1500); + JSON_ASSERT(e <= 1500); + const int f = kAlpha - e - 1; + const int k = (f * 78913) / (1 << 18) + static_cast(f > 0); + + const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; + JSON_ASSERT(index >= 0); + JSON_ASSERT(static_cast(index) < kCachedPowers.size()); + + const cached_power cached = kCachedPowers[static_cast(index)]; + JSON_ASSERT(kAlpha <= cached.e + e + 64); + JSON_ASSERT(kGamma >= cached.e + e + 64); + + return cached; +} + +/*! +For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. +For n == 0, returns 1 and sets pow10 := 1. +*/ +inline int find_largest_pow10(const std::uint32_t n, std::uint32_t& pow10) +{ + // LCOV_EXCL_START + if (n >= 1000000000) + { + pow10 = 1000000000; + return 10; + } + // LCOV_EXCL_STOP + else if (n >= 100000000) + { + pow10 = 100000000; + return 9; + } + else if (n >= 10000000) + { + pow10 = 10000000; + return 8; + } + else if (n >= 1000000) + { + pow10 = 1000000; + return 7; + } + else if (n >= 100000) + { + pow10 = 100000; + return 6; + } + else if (n >= 10000) + { + pow10 = 10000; + return 5; + } + else if (n >= 1000) + { + pow10 = 1000; + return 4; + } + else if (n >= 100) + { + pow10 = 100; + return 3; + } + else if (n >= 10) + { + pow10 = 10; + return 2; + } + else + { + pow10 = 1; + return 1; + } +} + +inline void grisu2_round(char* buf, int len, std::uint64_t dist, std::uint64_t delta, + std::uint64_t rest, std::uint64_t ten_k) +{ + JSON_ASSERT(len >= 1); + JSON_ASSERT(dist <= delta); + JSON_ASSERT(rest <= delta); + JSON_ASSERT(ten_k > 0); + + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // ten_k + // <------> + // <---- rest ----> + // --------------[------------------+----+--------------]-------------- + // w V + // = buf * 10^k + // + // ten_k represents a unit-in-the-last-place in the decimal representation + // stored in buf. + // Decrement buf by ten_k while this takes buf closer to w. + + // The tests are written in this order to avoid overflow in unsigned + // integer arithmetic. + + while (rest < dist + && delta - rest >= ten_k + && (rest + ten_k < dist || dist - rest > rest + ten_k - dist)) + { + JSON_ASSERT(buf[len - 1] != '0'); + buf[len - 1]--; + rest += ten_k; + } +} + +/*! +Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. +M- and M+ must be normalized and share the same exponent -60 <= e <= -32. +*/ +inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, + diyfp M_minus, diyfp w, diyfp M_plus) +{ + static_assert(kAlpha >= -60, "internal error"); + static_assert(kGamma <= -32, "internal error"); + + // Generates the digits (and the exponent) of a decimal floating-point + // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's + // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma. + // + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // Grisu2 generates the digits of M+ from left to right and stops as soon as + // V is in [M-,M+]. + + JSON_ASSERT(M_plus.e >= kAlpha); + JSON_ASSERT(M_plus.e <= kGamma); + + std::uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e) + std::uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e) + + // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): + // + // M+ = f * 2^e + // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e + // = ((p1 ) * 2^-e + (p2 )) * 2^e + // = p1 + p2 * 2^e + + const diyfp one(std::uint64_t{1} << -M_plus.e, M_plus.e); + + auto p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) + std::uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e + + // 1) + // + // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] + + JSON_ASSERT(p1 > 0); + + std::uint32_t pow10; + const int k = find_largest_pow10(p1, pow10); + + // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) + // + // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) + // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) + // + // M+ = p1 + p2 * 2^e + // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e + // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e + // = d[k-1] * 10^(k-1) + ( rest) * 2^e + // + // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) + // + // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] + // + // but stop as soon as + // + // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e + + int n = k; + while (n > 0) + { + // Invariants: + // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) + // pow10 = 10^(n-1) <= p1 < 10^n + // + const std::uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) + const std::uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) + // + // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e + // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) + // + JSON_ASSERT(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) + // + p1 = r; + n--; + // + // M+ = buffer * 10^n + (p1 + p2 * 2^e) + // pow10 = 10^n + // + + // Now check if enough digits have been generated. + // Compute + // + // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e + // + // Note: + // Since rest and delta share the same exponent e, it suffices to + // compare the significands. + const std::uint64_t rest = (std::uint64_t{p1} << -one.e) + p2; + if (rest <= delta) + { + // V = buffer * 10^n, with M- <= V <= M+. + + decimal_exponent += n; + + // We may now just stop. But instead look if the buffer could be + // decremented to bring V closer to w. + // + // pow10 = 10^n is now 1 ulp in the decimal representation V. + // The rounding procedure works with diyfp's with an implicit + // exponent of e. + // + // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e + // + const std::uint64_t ten_n = std::uint64_t{pow10} << -one.e; + grisu2_round(buffer, length, dist, delta, rest, ten_n); + + return; + } + + pow10 /= 10; + // + // pow10 = 10^(n-1) <= p1 < 10^n + // Invariants restored. + } + + // 2) + // + // The digits of the integral part have been generated: + // + // M+ = d[k-1]...d[1]d[0] + p2 * 2^e + // = buffer + p2 * 2^e + // + // Now generate the digits of the fractional part p2 * 2^e. + // + // Note: + // No decimal point is generated: the exponent is adjusted instead. + // + // p2 actually represents the fraction + // + // p2 * 2^e + // = p2 / 2^-e + // = d[-1] / 10^1 + d[-2] / 10^2 + ... + // + // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) + // + // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m + // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) + // + // using + // + // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) + // = ( d) * 2^-e + ( r) + // + // or + // 10^m * p2 * 2^e = d + r * 2^e + // + // i.e. + // + // M+ = buffer + p2 * 2^e + // = buffer + 10^-m * (d + r * 2^e) + // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e + // + // and stop as soon as 10^-m * r * 2^e <= delta * 2^e + + JSON_ASSERT(p2 > delta); + + int m = 0; + for (;;) + { + // Invariant: + // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e + // = buffer * 10^-m + 10^-m * (p2 ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e + // + JSON_ASSERT(p2 <= (std::numeric_limits::max)() / 10); + p2 *= 10; + const std::uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e + const std::uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e + // + // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) + // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + JSON_ASSERT(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + p2 = r; + m++; + // + // M+ = buffer * 10^-m + 10^-m * p2 * 2^e + // Invariant restored. + + // Check if enough digits have been generated. + // + // 10^-m * p2 * 2^e <= delta * 2^e + // p2 * 2^e <= 10^m * delta * 2^e + // p2 <= 10^m * delta + delta *= 10; + dist *= 10; + if (p2 <= delta) + { + break; + } + } + + // V = buffer * 10^-m, with M- <= V <= M+. + + decimal_exponent -= m; + + // 1 ulp in the decimal representation is now 10^-m. + // Since delta and dist are now scaled by 10^m, we need to do the + // same with ulp in order to keep the units in sync. + // + // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e + // + const std::uint64_t ten_m = one.f; + grisu2_round(buffer, length, dist, delta, p2, ten_m); + + // By construction this algorithm generates the shortest possible decimal + // number (Loitsch, Theorem 6.2) which rounds back to w. + // For an input number of precision p, at least + // + // N = 1 + ceil(p * log_10(2)) + // + // decimal digits are sufficient to identify all binary floating-point + // numbers (Matula, "In-and-Out conversions"). + // This implies that the algorithm does not produce more than N decimal + // digits. + // + // N = 17 for p = 53 (IEEE double precision) + // N = 9 for p = 24 (IEEE single precision) +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +JSON_HEDLEY_NON_NULL(1) +inline void grisu2(char* buf, int& len, int& decimal_exponent, + diyfp m_minus, diyfp v, diyfp m_plus) +{ + JSON_ASSERT(m_plus.e == m_minus.e); + JSON_ASSERT(m_plus.e == v.e); + + // --------(-----------------------+-----------------------)-------- (A) + // m- v m+ + // + // --------------------(-----------+-----------------------)-------- (B) + // m- v m+ + // + // First scale v (and m- and m+) such that the exponent is in the range + // [alpha, gamma]. + + const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); + + const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k + + // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma] + const diyfp w = diyfp::mul(v, c_minus_k); + const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); + const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); + + // ----(---+---)---------------(---+---)---------------(---+---)---- + // w- w w+ + // = c*m- = c*v = c*m+ + // + // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and + // w+ are now off by a small amount. + // In fact: + // + // w - v * 10^k < 1 ulp + // + // To account for this inaccuracy, add resp. subtract 1 ulp. + // + // --------+---[---------------(---+---)---------------]---+-------- + // w- M- w M+ w+ + // + // Now any number in [M-, M+] (bounds included) will round to w when input, + // regardless of how the input rounding algorithm breaks ties. + // + // And digit_gen generates the shortest possible such number in [M-, M+]. + // Note that this does not mean that Grisu2 always generates the shortest + // possible number in the interval (m-, m+). + const diyfp M_minus(w_minus.f + 1, w_minus.e); + const diyfp M_plus (w_plus.f - 1, w_plus.e ); + + decimal_exponent = -cached.k; // = -(-k) = k + + grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +template +JSON_HEDLEY_NON_NULL(1) +void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value) +{ + static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, + "internal error: not enough precision"); + + JSON_ASSERT(std::isfinite(value)); + JSON_ASSERT(value > 0); + + // If the neighbors (and boundaries) of 'value' are always computed for double-precision + // numbers, all float's can be recovered using strtod (and strtof). However, the resulting + // decimal representations are not exactly "short". + // + // The documentation for 'std::to_chars' (https://en.cppreference.com/w/cpp/utility/to_chars) + // says "value is converted to a string as if by std::sprintf in the default ("C") locale" + // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars' + // does. + // On the other hand, the documentation for 'std::to_chars' requires that "parsing the + // representation using the corresponding std::from_chars function recovers value exactly". That + // indicates that single precision floating-point numbers should be recovered using + // 'std::strtof'. + // + // NB: If the neighbors are computed for single-precision numbers, there is a single float + // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision + // value is off by 1 ulp. +#if 0 + const boundaries w = compute_boundaries(static_cast(value)); +#else + const boundaries w = compute_boundaries(value); +#endif + + grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); +} + +/*! +@brief appends a decimal representation of e to buf +@return a pointer to the element following the exponent. +@pre -1000 < e < 1000 +*/ +JSON_HEDLEY_NON_NULL(1) +JSON_HEDLEY_RETURNS_NON_NULL +inline char* append_exponent(char* buf, int e) +{ + JSON_ASSERT(e > -1000); + JSON_ASSERT(e < 1000); + + if (e < 0) + { + e = -e; + *buf++ = '-'; + } + else + { + *buf++ = '+'; + } + + auto k = static_cast(e); + if (k < 10) + { + // Always print at least two digits in the exponent. + // This is for compatibility with printf("%g"). + *buf++ = '0'; + *buf++ = static_cast('0' + k); + } + else if (k < 100) + { + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + else + { + *buf++ = static_cast('0' + k / 100); + k %= 100; + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + + return buf; +} + +/*! +@brief prettify v = buf * 10^decimal_exponent + +If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point +notation. Otherwise it will be printed in exponential notation. + +@pre min_exp < 0 +@pre max_exp > 0 +*/ +JSON_HEDLEY_NON_NULL(1) +JSON_HEDLEY_RETURNS_NON_NULL +inline char* format_buffer(char* buf, int len, int decimal_exponent, + int min_exp, int max_exp) +{ + JSON_ASSERT(min_exp < 0); + JSON_ASSERT(max_exp > 0); + + const int k = len; + const int n = len + decimal_exponent; + + // v = buf * 10^(n-k) + // k is the length of the buffer (number of decimal digits) + // n is the position of the decimal point relative to the start of the buffer. + + if (k <= n && n <= max_exp) + { + // digits[000] + // len <= max_exp + 2 + + std::memset(buf + k, '0', static_cast(n) - static_cast(k)); + // Make it look like a floating-point number (#362, #378) + buf[n + 0] = '.'; + buf[n + 1] = '0'; + return buf + (static_cast(n) + 2); + } + + if (0 < n && n <= max_exp) + { + // dig.its + // len <= max_digits10 + 1 + + JSON_ASSERT(k > n); + + std::memmove(buf + (static_cast(n) + 1), buf + n, static_cast(k) - static_cast(n)); + buf[n] = '.'; + return buf + (static_cast(k) + 1U); + } + + if (min_exp < n && n <= 0) + { + // 0.[000]digits + // len <= 2 + (-min_exp - 1) + max_digits10 + + std::memmove(buf + (2 + static_cast(-n)), buf, static_cast(k)); + buf[0] = '0'; + buf[1] = '.'; + std::memset(buf + 2, '0', static_cast(-n)); + return buf + (2U + static_cast(-n) + static_cast(k)); + } + + if (k == 1) + { + // dE+123 + // len <= 1 + 5 + + buf += 1; + } + else + { + // d.igitsE+123 + // len <= max_digits10 + 1 + 5 + + std::memmove(buf + 2, buf + 1, static_cast(k) - 1); + buf[1] = '.'; + buf += 1 + static_cast(k); + } + + *buf++ = 'e'; + return append_exponent(buf, n - 1); +} + +} // namespace dtoa_impl + +/*! +@brief generates a decimal representation of the floating-point number value in [first, last). + +The format of the resulting decimal representation is similar to printf's %g +format. Returns an iterator pointing past-the-end of the decimal representation. + +@note The input number must be finite, i.e. NaN's and Inf's are not supported. +@note The buffer must be large enough. +@note The result is NOT null-terminated. +*/ +template +JSON_HEDLEY_NON_NULL(1, 2) +JSON_HEDLEY_RETURNS_NON_NULL +char* to_chars(char* first, const char* last, FloatType value) +{ + static_cast(last); // maybe unused - fix warning + JSON_ASSERT(std::isfinite(value)); + + // Use signbit(value) instead of (value < 0) since signbit works for -0. + if (std::signbit(value)) + { + value = -value; + *first++ = '-'; + } + + if (value == 0) // +-0 + { + *first++ = '0'; + // Make it look like a floating-point number (#362, #378) + *first++ = '.'; + *first++ = '0'; + return first; + } + + JSON_ASSERT(last - first >= std::numeric_limits::max_digits10); + + // Compute v = buffer * 10^decimal_exponent. + // The decimal digits are stored in the buffer, which needs to be interpreted + // as an unsigned decimal integer. + // len is the length of the buffer, i.e. the number of decimal digits. + int len = 0; + int decimal_exponent = 0; + dtoa_impl::grisu2(first, len, decimal_exponent, value); + + JSON_ASSERT(len <= std::numeric_limits::max_digits10); + + // Format the buffer like printf("%.*g", prec, value) + constexpr int kMinExp = -4; + // Use digits10 here to increase compatibility with version 2. + constexpr int kMaxExp = std::numeric_limits::digits10; + + JSON_ASSERT(last - first >= kMaxExp + 2); + JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); + JSON_ASSERT(last - first >= std::numeric_limits::max_digits10 + 6); + + return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); +} + +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// serialization // +/////////////////// + +/// how to treat decoding errors +enum class error_handler_t +{ + strict, ///< throw a type_error exception in case of invalid UTF-8 + replace, ///< replace invalid UTF-8 sequences with U+FFFD + ignore ///< ignore invalid UTF-8 sequences +}; + +template +class serializer +{ + using string_t = typename BasicJsonType::string_t; + using number_float_t = typename BasicJsonType::number_float_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using binary_char_t = typename BasicJsonType::binary_t::value_type; + static constexpr std::uint8_t UTF8_ACCEPT = 0; + static constexpr std::uint8_t UTF8_REJECT = 1; + + public: + /*! + @param[in] s output stream to serialize to + @param[in] ichar indentation character to use + @param[in] error_handler_ how to react on decoding errors + */ + serializer(output_adapter_t s, const char ichar, + error_handler_t error_handler_ = error_handler_t::strict) + : o(std::move(s)) + , loc(std::localeconv()) + , thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->thousands_sep))) + , decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->decimal_point))) + , indent_char(ichar) + , indent_string(512, indent_char) + , error_handler(error_handler_) + {} + + // delete because of pointer members + serializer(const serializer&) = delete; + serializer& operator=(const serializer&) = delete; + serializer(serializer&&) = delete; + serializer& operator=(serializer&&) = delete; + ~serializer() = default; + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + - binary values are serialized as objects containing the subtype and the + byte array + + @param[in] val value to serialize + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters + in the output are escaped with `\uXXXX` sequences, and the result consists + of ASCII characters only. + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used internally) + */ + void dump(const BasicJsonType& val, + const bool pretty_print, + const bool ensure_ascii, + const unsigned int indent_step, + const unsigned int current_indent = 0) + { + switch (val.m_type) + { + case value_t::object: + { + if (val.m_value.object->empty()) + { + o->write_characters("{}", 2); + return; + } + + if (pretty_print) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + JSON_ASSERT(i != val.m_value.object->cend()); + JSON_ASSERT(std::next(i) == val.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_character('{'); + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + JSON_ASSERT(i != val.m_value.object->cend()); + JSON_ASSERT(std::next(i) == val.m_value.object->cend()); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + + o->write_character('}'); + } + + return; + } + + case value_t::array: + { + if (val.m_value.array->empty()) + { + o->write_characters("[]", 2); + return; + } + + if (pretty_print) + { + o->write_characters("[\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + dump(*i, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + JSON_ASSERT(!val.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character(']'); + } + else + { + o->write_character('['); + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + dump(*i, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + JSON_ASSERT(!val.m_value.array->empty()); + dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); + + o->write_character(']'); + } + + return; + } + + case value_t::string: + { + o->write_character('\"'); + dump_escaped(*val.m_value.string, ensure_ascii); + o->write_character('\"'); + return; + } + + case value_t::binary: + { + if (pretty_print) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + o->write_characters(indent_string.c_str(), new_indent); + + o->write_characters("\"bytes\": [", 10); + + if (!val.m_value.binary->empty()) + { + for (auto i = val.m_value.binary->cbegin(); + i != val.m_value.binary->cend() - 1; ++i) + { + dump_integer(*i); + o->write_characters(", ", 2); + } + dump_integer(val.m_value.binary->back()); + } + + o->write_characters("],\n", 3); + o->write_characters(indent_string.c_str(), new_indent); + + o->write_characters("\"subtype\": ", 11); + if (val.m_value.binary->has_subtype()) + { + dump_integer(val.m_value.binary->subtype()); + } + else + { + o->write_characters("null", 4); + } + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_characters("{\"bytes\":[", 10); + + if (!val.m_value.binary->empty()) + { + for (auto i = val.m_value.binary->cbegin(); + i != val.m_value.binary->cend() - 1; ++i) + { + dump_integer(*i); + o->write_character(','); + } + dump_integer(val.m_value.binary->back()); + } + + o->write_characters("],\"subtype\":", 12); + if (val.m_value.binary->has_subtype()) + { + dump_integer(val.m_value.binary->subtype()); + o->write_character('}'); + } + else + { + o->write_characters("null}", 5); + } + } + return; + } + + case value_t::boolean: + { + if (val.m_value.boolean) + { + o->write_characters("true", 4); + } + else + { + o->write_characters("false", 5); + } + return; + } + + case value_t::number_integer: + { + dump_integer(val.m_value.number_integer); + return; + } + + case value_t::number_unsigned: + { + dump_integer(val.m_value.number_unsigned); + return; + } + + case value_t::number_float: + { + dump_float(val.m_value.number_float); + return; + } + + case value_t::discarded: + { + o->write_characters("", 11); + return; + } + + case value_t::null: + { + o->write_characters("null", 4); + return; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + } + + private: + /*! + @brief dump escaped string + + Escape a string by replacing certain special characters by a sequence of an + escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. The escaped string is written to output stream @a o. + + @param[in] s the string to escape + @param[in] ensure_ascii whether to escape non-ASCII characters with + \uXXXX sequences + + @complexity Linear in the length of string @a s. + */ + void dump_escaped(const string_t& s, const bool ensure_ascii) + { + std::uint32_t codepoint; + std::uint8_t state = UTF8_ACCEPT; + std::size_t bytes = 0; // number of bytes written to string_buffer + + // number of bytes written at the point of the last valid byte + std::size_t bytes_after_last_accept = 0; + std::size_t undumped_chars = 0; + + for (std::size_t i = 0; i < s.size(); ++i) + { + const auto byte = static_cast(s[i]); + + switch (decode(state, codepoint, byte)) + { + case UTF8_ACCEPT: // decode found a new code point + { + switch (codepoint) + { + case 0x08: // backspace + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'b'; + break; + } + + case 0x09: // horizontal tab + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 't'; + break; + } + + case 0x0A: // newline + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'n'; + break; + } + + case 0x0C: // formfeed + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'f'; + break; + } + + case 0x0D: // carriage return + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'r'; + break; + } + + case 0x22: // quotation mark + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\"'; + break; + } + + case 0x5C: // reverse solidus + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\\'; + break; + } + + default: + { + // escape control characters (0x00..0x1F) or, if + // ensure_ascii parameter is used, non-ASCII characters + if ((codepoint <= 0x1F) || (ensure_ascii && (codepoint >= 0x7F))) + { + if (codepoint <= 0xFFFF) + { + (std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x", + static_cast(codepoint)); + bytes += 6; + } + else + { + (std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", + static_cast(0xD7C0u + (codepoint >> 10u)), + static_cast(0xDC00u + (codepoint & 0x3FFu))); + bytes += 12; + } + } + else + { + // copy byte to buffer (all previous bytes + // been copied have in default case above) + string_buffer[bytes++] = s[i]; + } + break; + } + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + + // remember the byte position of this accept + bytes_after_last_accept = bytes; + undumped_chars = 0; + break; + } + + case UTF8_REJECT: // decode found invalid UTF-8 byte + { + switch (error_handler) + { + case error_handler_t::strict: + { + std::string sn(3, '\0'); + (std::snprintf)(&sn[0], sn.size(), "%.2X", byte); + JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn)); + } + + case error_handler_t::ignore: + case error_handler_t::replace: + { + // in case we saw this character the first time, we + // would like to read it again, because the byte + // may be OK for itself, but just not OK for the + // previous sequence + if (undumped_chars > 0) + { + --i; + } + + // reset length buffer to the last accepted index; + // thus removing/ignoring the invalid characters + bytes = bytes_after_last_accept; + + if (error_handler == error_handler_t::replace) + { + // add a replacement character + if (ensure_ascii) + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'u'; + string_buffer[bytes++] = 'f'; + string_buffer[bytes++] = 'f'; + string_buffer[bytes++] = 'f'; + string_buffer[bytes++] = 'd'; + } + else + { + string_buffer[bytes++] = detail::binary_writer::to_char_type('\xEF'); + string_buffer[bytes++] = detail::binary_writer::to_char_type('\xBF'); + string_buffer[bytes++] = detail::binary_writer::to_char_type('\xBD'); + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + + bytes_after_last_accept = bytes; + } + + undumped_chars = 0; + + // continue processing the string + state = UTF8_ACCEPT; + break; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + break; + } + + default: // decode found yet incomplete multi-byte code point + { + if (!ensure_ascii) + { + // code point will not be escaped - copy byte to buffer + string_buffer[bytes++] = s[i]; + } + ++undumped_chars; + break; + } + } + } + + // we finished processing the string + if (JSON_HEDLEY_LIKELY(state == UTF8_ACCEPT)) + { + // write buffer + if (bytes > 0) + { + o->write_characters(string_buffer.data(), bytes); + } + } + else + { + // we finish reading, but do not accept: string was incomplete + switch (error_handler) + { + case error_handler_t::strict: + { + std::string sn(3, '\0'); + (std::snprintf)(&sn[0], sn.size(), "%.2X", static_cast(s.back())); + JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn)); + } + + case error_handler_t::ignore: + { + // write all accepted bytes + o->write_characters(string_buffer.data(), bytes_after_last_accept); + break; + } + + case error_handler_t::replace: + { + // write all accepted bytes + o->write_characters(string_buffer.data(), bytes_after_last_accept); + // add a replacement character + if (ensure_ascii) + { + o->write_characters("\\ufffd", 6); + } + else + { + o->write_characters("\xEF\xBF\xBD", 3); + } + break; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + } + } + + /*! + @brief count digits + + Count the number of decimal (base 10) digits for an input unsigned integer. + + @param[in] x unsigned integer number to count its digits + @return number of decimal digits + */ + inline unsigned int count_digits(number_unsigned_t x) noexcept + { + unsigned int n_digits = 1; + for (;;) + { + if (x < 10) + { + return n_digits; + } + if (x < 100) + { + return n_digits + 1; + } + if (x < 1000) + { + return n_digits + 2; + } + if (x < 10000) + { + return n_digits + 3; + } + x = x / 10000u; + n_digits += 4; + } + } + + /*! + @brief dump an integer + + Dump a given integer to output stream @a o. Works internally with + @a number_buffer. + + @param[in] x integer number (signed or unsigned) to dump + @tparam NumberType either @a number_integer_t or @a number_unsigned_t + */ + template < typename NumberType, detail::enable_if_t < + std::is_same::value || + std::is_same::value || + std::is_same::value, + int > = 0 > + void dump_integer(NumberType x) + { + static constexpr std::array, 100> digits_to_99 + { + { + {{'0', '0'}}, {{'0', '1'}}, {{'0', '2'}}, {{'0', '3'}}, {{'0', '4'}}, {{'0', '5'}}, {{'0', '6'}}, {{'0', '7'}}, {{'0', '8'}}, {{'0', '9'}}, + {{'1', '0'}}, {{'1', '1'}}, {{'1', '2'}}, {{'1', '3'}}, {{'1', '4'}}, {{'1', '5'}}, {{'1', '6'}}, {{'1', '7'}}, {{'1', '8'}}, {{'1', '9'}}, + {{'2', '0'}}, {{'2', '1'}}, {{'2', '2'}}, {{'2', '3'}}, {{'2', '4'}}, {{'2', '5'}}, {{'2', '6'}}, {{'2', '7'}}, {{'2', '8'}}, {{'2', '9'}}, + {{'3', '0'}}, {{'3', '1'}}, {{'3', '2'}}, {{'3', '3'}}, {{'3', '4'}}, {{'3', '5'}}, {{'3', '6'}}, {{'3', '7'}}, {{'3', '8'}}, {{'3', '9'}}, + {{'4', '0'}}, {{'4', '1'}}, {{'4', '2'}}, {{'4', '3'}}, {{'4', '4'}}, {{'4', '5'}}, {{'4', '6'}}, {{'4', '7'}}, {{'4', '8'}}, {{'4', '9'}}, + {{'5', '0'}}, {{'5', '1'}}, {{'5', '2'}}, {{'5', '3'}}, {{'5', '4'}}, {{'5', '5'}}, {{'5', '6'}}, {{'5', '7'}}, {{'5', '8'}}, {{'5', '9'}}, + {{'6', '0'}}, {{'6', '1'}}, {{'6', '2'}}, {{'6', '3'}}, {{'6', '4'}}, {{'6', '5'}}, {{'6', '6'}}, {{'6', '7'}}, {{'6', '8'}}, {{'6', '9'}}, + {{'7', '0'}}, {{'7', '1'}}, {{'7', '2'}}, {{'7', '3'}}, {{'7', '4'}}, {{'7', '5'}}, {{'7', '6'}}, {{'7', '7'}}, {{'7', '8'}}, {{'7', '9'}}, + {{'8', '0'}}, {{'8', '1'}}, {{'8', '2'}}, {{'8', '3'}}, {{'8', '4'}}, {{'8', '5'}}, {{'8', '6'}}, {{'8', '7'}}, {{'8', '8'}}, {{'8', '9'}}, + {{'9', '0'}}, {{'9', '1'}}, {{'9', '2'}}, {{'9', '3'}}, {{'9', '4'}}, {{'9', '5'}}, {{'9', '6'}}, {{'9', '7'}}, {{'9', '8'}}, {{'9', '9'}}, + } + }; + + // special case for "0" + if (x == 0) + { + o->write_character('0'); + return; + } + + // use a pointer to fill the buffer + auto buffer_ptr = number_buffer.begin(); + + const bool is_negative = std::is_same::value && !(x >= 0); // see issue #755 + number_unsigned_t abs_value; + + unsigned int n_chars; + + if (is_negative) + { + *buffer_ptr = '-'; + abs_value = remove_sign(static_cast(x)); + + // account one more byte for the minus sign + n_chars = 1 + count_digits(abs_value); + } + else + { + abs_value = static_cast(x); + n_chars = count_digits(abs_value); + } + + // spare 1 byte for '\0' + JSON_ASSERT(n_chars < number_buffer.size() - 1); + + // jump to the end to generate the string from backward + // so we later avoid reversing the result + buffer_ptr += n_chars; + + // Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu + // See: https://www.youtube.com/watch?v=o4-CwDo2zpg + while (abs_value >= 100) + { + const auto digits_index = static_cast((abs_value % 100)); + abs_value /= 100; + *(--buffer_ptr) = digits_to_99[digits_index][1]; + *(--buffer_ptr) = digits_to_99[digits_index][0]; + } + + if (abs_value >= 10) + { + const auto digits_index = static_cast(abs_value); + *(--buffer_ptr) = digits_to_99[digits_index][1]; + *(--buffer_ptr) = digits_to_99[digits_index][0]; + } + else + { + *(--buffer_ptr) = static_cast('0' + abs_value); + } + + o->write_characters(number_buffer.data(), n_chars); + } + + /*! + @brief dump a floating-point number + + Dump a given floating-point number to output stream @a o. Works internally + with @a number_buffer. + + @param[in] x floating-point number to dump + */ + void dump_float(number_float_t x) + { + // NaN / inf + if (!std::isfinite(x)) + { + o->write_characters("null", 4); + return; + } + + // If number_float_t is an IEEE-754 single or double precision number, + // use the Grisu2 algorithm to produce short numbers which are + // guaranteed to round-trip, using strtof and strtod, resp. + // + // NB: The test below works if == . + static constexpr bool is_ieee_single_or_double + = (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 24 && std::numeric_limits::max_exponent == 128) || + (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 53 && std::numeric_limits::max_exponent == 1024); + + dump_float(x, std::integral_constant()); + } + + void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) + { + char* begin = number_buffer.data(); + char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); + + o->write_characters(begin, static_cast(end - begin)); + } + + void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) + { + // get number of digits for a float -> text -> float round-trip + static constexpr auto d = std::numeric_limits::max_digits10; + + // the actual conversion + std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(), "%.*g", d, x); + + // negative value indicates an error + JSON_ASSERT(len > 0); + // check if buffer was large enough + JSON_ASSERT(static_cast(len) < number_buffer.size()); + + // erase thousands separator + if (thousands_sep != '\0') + { + const auto end = std::remove(number_buffer.begin(), + number_buffer.begin() + len, thousands_sep); + std::fill(end, number_buffer.end(), '\0'); + JSON_ASSERT((end - number_buffer.begin()) <= len); + len = (end - number_buffer.begin()); + } + + // convert decimal point to '.' + if (decimal_point != '\0' && decimal_point != '.') + { + const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); + if (dec_pos != number_buffer.end()) + { + *dec_pos = '.'; + } + } + + o->write_characters(number_buffer.data(), static_cast(len)); + + // determine if need to append ".0" + const bool value_is_int_like = + std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, + [](char c) + { + return c == '.' || c == 'e'; + }); + + if (value_is_int_like) + { + o->write_characters(".0", 2); + } + } + + /*! + @brief check whether a string is UTF-8 encoded + + The function checks each byte of a string whether it is UTF-8 encoded. The + result of the check is stored in the @a state parameter. The function must + be called initially with state 0 (accept). State 1 means the string must + be rejected, because the current byte is not allowed. If the string is + completely processed, but the state is non-zero, the string ended + prematurely; that is, the last byte indicated more bytes should have + followed. + + @param[in,out] state the state of the decoding + @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) + @param[in] byte next byte to decode + @return new state + + @note The function has been edited: a std::array is used. + + @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann + @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + */ + static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept + { + static const std::array utf8d = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF + 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF + 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 + } + }; + + const std::uint8_t type = utf8d[byte]; + + codep = (state != UTF8_ACCEPT) + ? (byte & 0x3fu) | (codep << 6u) + : (0xFFu >> type) & (byte); + + std::size_t index = 256u + static_cast(state) * 16u + static_cast(type); + JSON_ASSERT(index < 400); + state = utf8d[index]; + return state; + } + + /* + * Overload to make the compiler happy while it is instantiating + * dump_integer for number_unsigned_t. + * Must never be called. + */ + number_unsigned_t remove_sign(number_unsigned_t x) + { + JSON_ASSERT(false); // LCOV_EXCL_LINE + return x; // LCOV_EXCL_LINE + } + + /* + * Helper function for dump_integer + * + * This function takes a negative signed integer and returns its absolute + * value as unsigned integer. The plus/minus shuffling is necessary as we can + * not directly remove the sign of an arbitrary signed integer as the + * absolute values of INT_MIN and INT_MAX are usually not the same. See + * #1708 for details. + */ + inline number_unsigned_t remove_sign(number_integer_t x) noexcept + { + JSON_ASSERT(x < 0 && x < (std::numeric_limits::max)()); + return static_cast(-(x + 1)) + 1; + } + + private: + /// the output of the serializer + output_adapter_t o = nullptr; + + /// a (hopefully) large enough character buffer + std::array number_buffer{{}}; + + /// the locale + const std::lconv* loc = nullptr; + /// the locale's thousand separator character + const char thousands_sep = '\0'; + /// the locale's decimal point character + const char decimal_point = '\0'; + + /// string buffer + std::array string_buffer{{}}; + + /// the indentation character + const char indent_char; + /// the indentation string + string_t indent_string; + + /// error_handler how to react on decoding errors + const error_handler_t error_handler; +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + +// #include + + +#include // less +#include // allocator +#include // pair +#include // vector + +namespace nlohmann +{ + +/// ordered_map: a minimal map-like container that preserves insertion order +/// for use within nlohmann::basic_json +template , + class Allocator = std::allocator>> + struct ordered_map : std::vector, Allocator> +{ + using key_type = Key; + using mapped_type = T; + using Container = std::vector, Allocator>; + using typename Container::iterator; + using typename Container::const_iterator; + using typename Container::size_type; + using typename Container::value_type; + + // Explicit constructors instead of `using Container::Container` + // otherwise older compilers choke on it (GCC <= 5.5, xcode <= 9.4) + ordered_map(const Allocator& alloc = Allocator()) : Container{alloc} {} + template + ordered_map(It first, It last, const Allocator& alloc = Allocator()) + : Container{first, last, alloc} {} + ordered_map(std::initializer_list init, const Allocator& alloc = Allocator() ) + : Container{init, alloc} {} + + std::pair emplace(const key_type& key, T&& t) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return {it, false}; + } + } + Container::emplace_back(key, t); + return {--this->end(), true}; + } + + T& operator[](const Key& key) + { + return emplace(key, T{}).first->second; + } + + const T& operator[](const Key& key) const + { + return at(key); + } + + T& at(const Key& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it->second; + } + } + + throw std::out_of_range("key not found"); + } + + const T& at(const Key& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it->second; + } + } + + throw std::out_of_range("key not found"); + } + + size_type erase(const Key& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + // Since we cannot move const Keys, re-construct them in place + for (auto next = it; ++next != this->end(); ++it) + { + it->~value_type(); // Destroy but keep allocation + new (&*it) value_type{std::move(*next)}; + } + Container::pop_back(); + return 1; + } + } + return 0; + } + + iterator erase(iterator pos) + { + auto it = pos; + + // Since we cannot move const Keys, re-construct them in place + for (auto next = it; ++next != this->end(); ++it) + { + it->~value_type(); // Destroy but keep allocation + new (&*it) value_type{std::move(*next)}; + } + Container::pop_back(); + return pos; + } + + size_type count(const Key& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return 1; + } + } + return 0; + } + + iterator find(const Key& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it; + } + } + return Container::end(); + } + + const_iterator find(const Key& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it; + } + } + return Container::end(); + } + + std::pair insert( value_type&& value ) + { + return emplace(value.first, std::move(value.second)); + } + + std::pair insert( const value_type& value ) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == value.first) + { + return {it, false}; + } + } + Container::push_back(value); + return {--this->end(), true}; + } +}; + +} // namespace nlohmann + + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ + +/*! +@brief a class to store JSON values + +@tparam ObjectType type for JSON objects (`std::map` by default; will be used +in @ref object_t) +@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used +in @ref array_t) +@tparam StringType type for JSON strings and object keys (`std::string` by +default; will be used in @ref string_t) +@tparam BooleanType type for JSON booleans (`bool` by default; will be used +in @ref boolean_t) +@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by +default; will be used in @ref number_integer_t) +@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c +`uint64_t` by default; will be used in @ref number_unsigned_t) +@tparam NumberFloatType type for JSON floating-point numbers (`double` by +default; will be used in @ref number_float_t) +@tparam BinaryType type for packed binary data for compatibility with binary +serialization formats (`std::vector` by default; will be used in +@ref binary_t) +@tparam AllocatorType type of the allocator to use (`std::allocator` by +default) +@tparam JSONSerializer the serializer to resolve internal calls to `to_json()` +and `from_json()` (@ref adl_serializer by default) + +@requirement The class satisfies the following concept requirements: +- Basic + - [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible): + JSON values can be default constructed. The result will be a JSON null + value. + - [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible): + A JSON value can be constructed from an rvalue argument. + - [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible): + A JSON value can be copy-constructed from an lvalue expression. + - [MoveAssignable](https://en.cppreference.com/w/cpp/named_req/MoveAssignable): + A JSON value van be assigned from an rvalue argument. + - [CopyAssignable](https://en.cppreference.com/w/cpp/named_req/CopyAssignable): + A JSON value can be copy-assigned from an lvalue expression. + - [Destructible](https://en.cppreference.com/w/cpp/named_req/Destructible): + JSON values can be destructed. +- Layout + - [StandardLayoutType](https://en.cppreference.com/w/cpp/named_req/StandardLayoutType): + JSON values have + [standard layout](https://en.cppreference.com/w/cpp/language/data_members#Standard_layout): + All non-static data members are private and standard layout types, the + class has no virtual functions or (virtual) base classes. +- Library-wide + - [EqualityComparable](https://en.cppreference.com/w/cpp/named_req/EqualityComparable): + JSON values can be compared with `==`, see @ref + operator==(const_reference,const_reference). + - [LessThanComparable](https://en.cppreference.com/w/cpp/named_req/LessThanComparable): + JSON values can be compared with `<`, see @ref + operator<(const_reference,const_reference). + - [Swappable](https://en.cppreference.com/w/cpp/named_req/Swappable): + Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of + other compatible types, using unqualified function call @ref swap(). + - [NullablePointer](https://en.cppreference.com/w/cpp/named_req/NullablePointer): + JSON values can be compared against `std::nullptr_t` objects which are used + to model the `null` value. +- Container + - [Container](https://en.cppreference.com/w/cpp/named_req/Container): + JSON values can be used like STL containers and provide iterator access. + - [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer); + JSON values can be used like STL containers and provide reverse iterator + access. + +@invariant The member variables @a m_value and @a m_type have the following +relationship: +- If `m_type == value_t::object`, then `m_value.object != nullptr`. +- If `m_type == value_t::array`, then `m_value.array != nullptr`. +- If `m_type == value_t::string`, then `m_value.string != nullptr`. +The invariants are checked by member function assert_invariant(). + +@internal +@note ObjectType trick from https://stackoverflow.com/a/9860911 +@endinternal + +@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange +Format](http://rfc7159.net/rfc7159) + +@since version 1.0.0 + +@nosubgrouping +*/ +NLOHMANN_BASIC_JSON_TPL_DECLARATION +class basic_json +{ + private: + template friend struct detail::external_constructor; + friend ::nlohmann::json_pointer; + + template + friend class ::nlohmann::detail::parser; + friend ::nlohmann::detail::serializer; + template + friend class ::nlohmann::detail::iter_impl; + template + friend class ::nlohmann::detail::binary_writer; + template + friend class ::nlohmann::detail::binary_reader; + template + friend class ::nlohmann::detail::json_sax_dom_parser; + template + friend class ::nlohmann::detail::json_sax_dom_callback_parser; + + /// workaround type for MSVC + using basic_json_t = NLOHMANN_BASIC_JSON_TPL; + + // convenience aliases for types residing in namespace detail; + using lexer = ::nlohmann::detail::lexer_base; + + template + static ::nlohmann::detail::parser parser( + InputAdapterType adapter, + detail::parser_callback_tcb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false + ) + { + return ::nlohmann::detail::parser(std::move(adapter), + std::move(cb), allow_exceptions, ignore_comments); + } + + using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; + template + using internal_iterator = ::nlohmann::detail::internal_iterator; + template + using iter_impl = ::nlohmann::detail::iter_impl; + template + using iteration_proxy = ::nlohmann::detail::iteration_proxy; + template using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator; + + template + using output_adapter_t = ::nlohmann::detail::output_adapter_t; + + template + using binary_reader = ::nlohmann::detail::binary_reader; + template using binary_writer = ::nlohmann::detail::binary_writer; + + using serializer = ::nlohmann::detail::serializer; + + public: + using value_t = detail::value_t; + /// JSON Pointer, see @ref nlohmann::json_pointer + using json_pointer = ::nlohmann::json_pointer; + template + using json_serializer = JSONSerializer; + /// how to treat decoding errors + using error_handler_t = detail::error_handler_t; + /// how to treat CBOR tags + using cbor_tag_handler_t = detail::cbor_tag_handler_t; + /// helper type for initializer lists of basic_json values + using initializer_list_t = std::initializer_list>; + + using input_format_t = detail::input_format_t; + /// SAX interface type, see @ref nlohmann::json_sax + using json_sax_t = json_sax; + + //////////////// + // exceptions // + //////////////// + + /// @name exceptions + /// Classes to implement user-defined exceptions. + /// @{ + + /// @copydoc detail::exception + using exception = detail::exception; + /// @copydoc detail::parse_error + using parse_error = detail::parse_error; + /// @copydoc detail::invalid_iterator + using invalid_iterator = detail::invalid_iterator; + /// @copydoc detail::type_error + using type_error = detail::type_error; + /// @copydoc detail::out_of_range + using out_of_range = detail::out_of_range; + /// @copydoc detail::other_error + using other_error = detail::other_error; + + /// @} + + + ///////////////////// + // container types // + ///////////////////// + + /// @name container types + /// The canonic container types to use @ref basic_json like any other STL + /// container. + /// @{ + + /// the type of elements in a basic_json container + using value_type = basic_json; + + /// the type of an element reference + using reference = value_type&; + /// the type of an element const reference + using const_reference = const value_type&; + + /// a type to represent differences between iterators + using difference_type = std::ptrdiff_t; + /// a type to represent container sizes + using size_type = std::size_t; + + /// the allocator type + using allocator_type = AllocatorType; + + /// the type of an element pointer + using pointer = typename std::allocator_traits::pointer; + /// the type of an element const pointer + using const_pointer = typename std::allocator_traits::const_pointer; + + /// an iterator for a basic_json container + using iterator = iter_impl; + /// a const iterator for a basic_json container + using const_iterator = iter_impl; + /// a reverse iterator for a basic_json container + using reverse_iterator = json_reverse_iterator; + /// a const reverse iterator for a basic_json container + using const_reverse_iterator = json_reverse_iterator; + + /// @} + + + /*! + @brief returns the allocator associated with the container + */ + static allocator_type get_allocator() + { + return allocator_type(); + } + + /*! + @brief returns version information on the library + + This function returns a JSON object with information about the library, + including the version number and information on the platform and compiler. + + @return JSON object holding version information + key | description + ----------- | --------------- + `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). + `copyright` | The copyright line for the library as string. + `name` | The name of the library as string. + `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`. + `url` | The URL of the project as string. + `version` | The version of the library. It is an object with the following keys: `major`, `minor`, and `patch` as defined by [Semantic Versioning](http://semver.org), and `string` (the version string). + + @liveexample{The following code shows an example output of the `meta()` + function.,meta} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @complexity Constant. + + @since 2.1.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json meta() + { + basic_json result; + + result["copyright"] = "(C) 2013-2020 Niels Lohmann"; + result["name"] = "JSON for Modern C++"; + result["url"] = "https://github.com/nlohmann/json"; + result["version"]["string"] = + std::to_string(NLOHMANN_JSON_VERSION_MAJOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_MINOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_PATCH); + result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; + result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; + result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; + +#ifdef _WIN32 + result["platform"] = "win32"; +#elif defined __linux__ + result["platform"] = "linux"; +#elif defined __APPLE__ + result["platform"] = "apple"; +#elif defined __unix__ + result["platform"] = "unix"; +#else + result["platform"] = "unknown"; +#endif + +#if defined(__ICC) || defined(__INTEL_COMPILER) + result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; +#elif defined(__clang__) + result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; +#elif defined(__GNUC__) || defined(__GNUG__) + result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}}; +#elif defined(__HP_cc) || defined(__HP_aCC) + result["compiler"] = "hp" +#elif defined(__IBMCPP__) + result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; +#elif defined(_MSC_VER) + result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; +#elif defined(__PGI) + result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; +#elif defined(__SUNPRO_CC) + result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; +#else + result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; +#endif + +#ifdef __cplusplus + result["compiler"]["c++"] = std::to_string(__cplusplus); +#else + result["compiler"]["c++"] = "unknown"; +#endif + return result; + } + + + /////////////////////////// + // JSON value data types // + /////////////////////////// + + /// @name JSON value data types + /// The data types to store a JSON value. These types are derived from + /// the template arguments passed to class @ref basic_json. + /// @{ + +#if defined(JSON_HAS_CPP_14) + // Use transparent comparator if possible, combined with perfect forwarding + // on find() and count() calls prevents unnecessary string construction. + using object_comparator_t = std::less<>; +#else + using object_comparator_t = std::less; +#endif + + /*! + @brief a type for an object + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: + > An object is an unordered collection of zero or more name/value pairs, + > where a name is a string and a value is a string, number, boolean, null, + > object, or array. + + To store objects in C++, a type is defined by the template parameters + described below. + + @tparam ObjectType the container to store objects (e.g., `std::map` or + `std::unordered_map`) + @tparam StringType the type of the keys or names (e.g., `std::string`). + The comparison function `std::less` is used to order elements + inside the container. + @tparam AllocatorType the allocator to use for objects (e.g., + `std::allocator`) + + #### Default type + + With the default values for @a ObjectType (`std::map`), @a StringType + (`std::string`), and @a AllocatorType (`std::allocator`), the default + value for @a object_t is: + + @code {.cpp} + std::map< + std::string, // key_type + basic_json, // value_type + std::less, // key_compare + std::allocator> // allocator_type + > + @endcode + + #### Behavior + + The choice of @a object_t influences the behavior of the JSON class. With + the default type, objects have the following behavior: + + - When all names are unique, objects will be interoperable in the sense + that all software implementations receiving that object will agree on + the name-value mappings. + - When the names within an object are not unique, it is unspecified which + one of the values for a given key will be chosen. For instance, + `{"key": 2, "key": 1}` could be equal to either `{"key": 1}` or + `{"key": 2}`. + - Internally, name/value pairs are stored in lexicographical order of the + names. Objects will also be serialized (see @ref dump) in this order. + For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored + and serialized as `{"a": 2, "b": 1}`. + - When comparing objects, the order of the name/value pairs is irrelevant. + This makes objects interoperable in the sense that they will not be + affected by these differences. For instance, `{"b": 1, "a": 2}` and + `{"a": 2, "b": 1}` will be treated as equal. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the object's limit of nesting is not explicitly constrained. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON object. + + #### Storage + + Objects are stored as pointers in a @ref basic_json type. That is, for any + access to object values, a pointer of type `object_t*` must be + dereferenced. + + @sa @ref array_t -- type for an array value + + @since version 1.0.0 + + @note The order name/value pairs are added to the object is *not* + preserved by the library. Therefore, iterating an object may return + name/value pairs in a different order than they were originally stored. In + fact, keys will be traversed in alphabetical order as `std::map` with + `std::less` is used by default. Please note this behavior conforms to [RFC + 7159](http://rfc7159.net/rfc7159), because any order implements the + specified "unordered" nature of JSON objects. + */ + using object_t = ObjectType>>; + + /*! + @brief a type for an array + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: + > An array is an ordered sequence of zero or more values. + + To store objects in C++, a type is defined by the template parameters + explained below. + + @tparam ArrayType container type to store arrays (e.g., `std::vector` or + `std::list`) + @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) + + #### Default type + + With the default values for @a ArrayType (`std::vector`) and @a + AllocatorType (`std::allocator`), the default value for @a array_t is: + + @code {.cpp} + std::vector< + basic_json, // value_type + std::allocator // allocator_type + > + @endcode + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the array's limit of nesting is not explicitly constrained. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON array. + + #### Storage + + Arrays are stored as pointers in a @ref basic_json type. That is, for any + access to array values, a pointer of type `array_t*` must be dereferenced. + + @sa @ref object_t -- type for an object value + + @since version 1.0.0 + */ + using array_t = ArrayType>; + + /*! + @brief a type for a string + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: + > A string is a sequence of zero or more Unicode characters. + + To store objects in C++, a type is defined by the template parameter + described below. Unicode values are split by the JSON class into + byte-sized characters during deserialization. + + @tparam StringType the container to store strings (e.g., `std::string`). + Note this container is used for keys/names in objects, see @ref object_t. + + #### Default type + + With the default values for @a StringType (`std::string`), the default + value for @a string_t is: + + @code {.cpp} + std::string + @endcode + + #### Encoding + + Strings are stored in UTF-8 encoding. Therefore, functions like + `std::string::size()` or `std::string::length()` return the number of + bytes in the string rather than the number of characters or glyphs. + + #### String comparison + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > Software implementations are typically required to test names of object + > members for equality. Implementations that transform the textual + > representation into sequences of Unicode code units and then perform the + > comparison numerically, code unit by code unit, are interoperable in the + > sense that implementations will agree in all cases on equality or + > inequality of two strings. For example, implementations that compare + > strings with escaped characters unconverted may incorrectly find that + > `"a\\b"` and `"a\u005Cb"` are not equal. + + This implementation is interoperable as it does compare strings code unit + by code unit. + + #### Storage + + String values are stored as pointers in a @ref basic_json type. That is, + for any access to string values, a pointer of type `string_t*` must be + dereferenced. + + @since version 1.0.0 + */ + using string_t = StringType; + + /*! + @brief a type for a boolean + + [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a + type which differentiates the two literals `true` and `false`. + + To store objects in C++, a type is defined by the template parameter @a + BooleanType which chooses the type to use. + + #### Default type + + With the default values for @a BooleanType (`bool`), the default value for + @a boolean_t is: + + @code {.cpp} + bool + @endcode + + #### Storage + + Boolean values are stored directly inside a @ref basic_json type. + + @since version 1.0.0 + */ + using boolean_t = BooleanType; + + /*! + @brief a type for a number (integer) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store integer numbers in C++, a type is defined by the template + parameter @a NumberIntegerType which chooses the type to use. + + #### Default type + + With the default values for @a NumberIntegerType (`int64_t`), the default + value for @a number_integer_t is: + + @code {.cpp} + int64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `9223372036854775807` (INT64_MAX) and the minimal integer number + that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers + that are out of range will yield over/underflow when used in a + constructor. During deserialization, too large or small integer numbers + will be automatically be stored as @ref number_unsigned_t or @ref + number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange of the exactly supported range [INT64_MIN, + INT64_MAX], this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_integer_t = NumberIntegerType; + + /*! + @brief a type for a number (unsigned) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store unsigned integer numbers in C++, a type is defined by the + template parameter @a NumberUnsignedType which chooses the type to use. + + #### Default type + + With the default values for @a NumberUnsignedType (`uint64_t`), the + default value for @a number_unsigned_t is: + + @code {.cpp} + uint64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `18446744073709551615` (UINT64_MAX) and the minimal integer + number that can be stored is `0`. Integer numbers that are out of range + will yield over/underflow when used in a constructor. During + deserialization, too large or small integer numbers will be automatically + be stored as @ref number_integer_t or @ref number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange (when considered in conjunction with the + number_integer_t type) of the exactly supported range [0, UINT64_MAX], + this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + @sa @ref number_integer_t -- type for number values (integer) + + @since version 2.0.0 + */ + using number_unsigned_t = NumberUnsignedType; + + /*! + @brief a type for a number (floating-point) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store floating-point numbers in C++, a type is defined by the template + parameter @a NumberFloatType which chooses the type to use. + + #### Default type + + With the default values for @a NumberFloatType (`double`), the default + value for @a number_float_t is: + + @code {.cpp} + double + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in floating-point literals will be ignored. Internally, + the value will be stored as decimal number. For instance, the C++ + floating-point literal `01.2` will be serialized to `1.2`. During + deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > This specification allows implementations to set limits on the range and + > precision of numbers accepted. Since software that implements IEEE + > 754-2008 binary64 (double precision) numbers is generally available and + > widely used, good interoperability can be achieved by implementations + > that expect no more precision or range than these provide, in the sense + > that implementations will approximate JSON numbers within the expected + > precision. + + This implementation does exactly follow this approach, as it uses double + precision floating-point numbers. Note values smaller than + `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` + will be stored as NaN internally and be serialized to `null`. + + #### Storage + + Floating-point number values are stored directly inside a @ref basic_json + type. + + @sa @ref number_integer_t -- type for number values (integer) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_float_t = NumberFloatType; + + /*! + @brief a type for a packed binary type + + This type is a type designed to carry binary data that appears in various + serialized formats, such as CBOR's Major Type 2, MessagePack's bin, and + BSON's generic binary subtype. This type is NOT a part of standard JSON and + exists solely for compatibility with these binary types. As such, it is + simply defined as an ordered sequence of zero or more byte values. + + Additionally, as an implementation detail, the subtype of the binary data is + carried around as a `std::uint8_t`, which is compatible with both of the + binary data formats that use binary subtyping, (though the specific + numbering is incompatible with each other, and it is up to the user to + translate between them). + + [CBOR's RFC 7049](https://tools.ietf.org/html/rfc7049) describes this type + as: + > Major type 2: a byte string. The string's length in bytes is represented + > following the rules for positive integers (major type 0). + + [MessagePack's documentation on the bin type + family](https://github.com/msgpack/msgpack/blob/master/spec.md#bin-format-family) + describes this type as: + > Bin format family stores an byte array in 2, 3, or 5 bytes of extra bytes + > in addition to the size of the byte array. + + [BSON's specifications](http://bsonspec.org/spec.html) describe several + binary types; however, this type is intended to represent the generic binary + type which has the description: + > Generic binary subtype - This is the most commonly used binary subtype and + > should be the 'default' for drivers and tools. + + None of these impose any limitations on the internal representation other + than the basic unit of storage be some type of array whose parts are + decomposable into bytes. + + The default representation of this binary format is a + `std::vector`, which is a very common way to represent a byte + array in modern C++. + + #### Default type + + The default values for @a BinaryType is `std::vector` + + #### Storage + + Binary Arrays are stored as pointers in a @ref basic_json type. That is, + for any access to array values, a pointer of the type `binary_t*` must be + dereferenced. + + #### Notes on subtypes + + - CBOR + - Binary values are represented as byte strings. No subtypes are + supported and will be ignored when CBOR is written. + - MessagePack + - If a subtype is given and the binary array contains exactly 1, 2, 4, 8, + or 16 elements, the fixext family (fixext1, fixext2, fixext4, fixext8) + is used. For other sizes, the ext family (ext8, ext16, ext32) is used. + The subtype is then added as singed 8-bit integer. + - If no subtype is given, the bin family (bin8, bin16, bin32) is used. + - BSON + - If a subtype is given, it is used and added as unsigned 8-bit integer. + - If no subtype is given, the generic binary subtype 0x00 is used. + + @sa @ref binary -- create a binary array + + @since version 3.8.0 + */ + using binary_t = nlohmann::byte_container_with_subtype; + /// @} + + private: + + /// helper for exception-safe object creation + template + JSON_HEDLEY_RETURNS_NON_NULL + static T* create(Args&& ... args) + { + AllocatorType alloc; + using AllocatorTraits = std::allocator_traits>; + + auto deleter = [&](T * object) + { + AllocatorTraits::deallocate(alloc, object, 1); + }; + std::unique_ptr object(AllocatorTraits::allocate(alloc, 1), deleter); + AllocatorTraits::construct(alloc, object.get(), std::forward(args)...); + JSON_ASSERT(object != nullptr); + return object.release(); + } + + //////////////////////// + // JSON value storage // + //////////////////////// + + /*! + @brief a JSON value + + The actual storage for a JSON value of the @ref basic_json class. This + union combines the different storage types for the JSON value types + defined in @ref value_t. + + JSON type | value_t type | used type + --------- | --------------- | ------------------------ + object | object | pointer to @ref object_t + array | array | pointer to @ref array_t + string | string | pointer to @ref string_t + boolean | boolean | @ref boolean_t + number | number_integer | @ref number_integer_t + number | number_unsigned | @ref number_unsigned_t + number | number_float | @ref number_float_t + binary | binary | pointer to @ref binary_t + null | null | *no value is stored* + + @note Variable-length types (objects, arrays, and strings) are stored as + pointers. The size of the union should not exceed 64 bits if the default + value types are used. + + @since version 1.0.0 + */ + union json_value + { + /// object (stored with pointer to save storage) + object_t* object; + /// array (stored with pointer to save storage) + array_t* array; + /// string (stored with pointer to save storage) + string_t* string; + /// binary (stored with pointer to save storage) + binary_t* binary; + /// boolean + boolean_t boolean; + /// number (integer) + number_integer_t number_integer; + /// number (unsigned integer) + number_unsigned_t number_unsigned; + /// number (floating-point) + number_float_t number_float; + + /// default constructor (for null values) + json_value() = default; + /// constructor for booleans + json_value(boolean_t v) noexcept : boolean(v) {} + /// constructor for numbers (integer) + json_value(number_integer_t v) noexcept : number_integer(v) {} + /// constructor for numbers (unsigned) + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} + /// constructor for numbers (floating-point) + json_value(number_float_t v) noexcept : number_float(v) {} + /// constructor for empty values of a given type + json_value(value_t t) + { + switch (t) + { + case value_t::object: + { + object = create(); + break; + } + + case value_t::array: + { + array = create(); + break; + } + + case value_t::string: + { + string = create(""); + break; + } + + case value_t::binary: + { + binary = create(); + break; + } + + case value_t::boolean: + { + boolean = boolean_t(false); + break; + } + + case value_t::number_integer: + { + number_integer = number_integer_t(0); + break; + } + + case value_t::number_unsigned: + { + number_unsigned = number_unsigned_t(0); + break; + } + + case value_t::number_float: + { + number_float = number_float_t(0.0); + break; + } + + case value_t::null: + { + object = nullptr; // silence warning, see #821 + break; + } + + default: + { + object = nullptr; // silence warning, see #821 + if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) + { + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.9.1")); // LCOV_EXCL_LINE + } + break; + } + } + } + + /// constructor for strings + json_value(const string_t& value) + { + string = create(value); + } + + /// constructor for rvalue strings + json_value(string_t&& value) + { + string = create(std::move(value)); + } + + /// constructor for objects + json_value(const object_t& value) + { + object = create(value); + } + + /// constructor for rvalue objects + json_value(object_t&& value) + { + object = create(std::move(value)); + } + + /// constructor for arrays + json_value(const array_t& value) + { + array = create(value); + } + + /// constructor for rvalue arrays + json_value(array_t&& value) + { + array = create(std::move(value)); + } + + /// constructor for binary arrays + json_value(const typename binary_t::container_type& value) + { + binary = create(value); + } + + /// constructor for rvalue binary arrays + json_value(typename binary_t::container_type&& value) + { + binary = create(std::move(value)); + } + + /// constructor for binary arrays (internal type) + json_value(const binary_t& value) + { + binary = create(value); + } + + /// constructor for rvalue binary arrays (internal type) + json_value(binary_t&& value) + { + binary = create(std::move(value)); + } + + void destroy(value_t t) noexcept + { + // flatten the current json_value to a heap-allocated stack + std::vector stack; + + // move the top-level items to stack + if (t == value_t::array) + { + stack.reserve(array->size()); + std::move(array->begin(), array->end(), std::back_inserter(stack)); + } + else if (t == value_t::object) + { + stack.reserve(object->size()); + for (auto&& it : *object) + { + stack.push_back(std::move(it.second)); + } + } + + while (!stack.empty()) + { + // move the last item to local variable to be processed + basic_json current_item(std::move(stack.back())); + stack.pop_back(); + + // if current_item is array/object, move + // its children to the stack to be processed later + if (current_item.is_array()) + { + std::move(current_item.m_value.array->begin(), current_item.m_value.array->end(), + std::back_inserter(stack)); + + current_item.m_value.array->clear(); + } + else if (current_item.is_object()) + { + for (auto&& it : *current_item.m_value.object) + { + stack.push_back(std::move(it.second)); + } + + current_item.m_value.object->clear(); + } + + // it's now safe that current_item get destructed + // since it doesn't have any children + } + + switch (t) + { + case value_t::object: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, object); + std::allocator_traits::deallocate(alloc, object, 1); + break; + } + + case value_t::array: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, array); + std::allocator_traits::deallocate(alloc, array, 1); + break; + } + + case value_t::string: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, string); + std::allocator_traits::deallocate(alloc, string, 1); + break; + } + + case value_t::binary: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, binary); + std::allocator_traits::deallocate(alloc, binary, 1); + break; + } + + default: + { + break; + } + } + } + }; + + /*! + @brief checks the class invariants + + This function asserts the class invariants. It needs to be called at the + end of every constructor to make sure that created objects respect the + invariant. Furthermore, it has to be called each time the type of a JSON + value is changed, because the invariant expresses a relationship between + @a m_type and @a m_value. + */ + void assert_invariant() const noexcept + { + JSON_ASSERT(m_type != value_t::object || m_value.object != nullptr); + JSON_ASSERT(m_type != value_t::array || m_value.array != nullptr); + JSON_ASSERT(m_type != value_t::string || m_value.string != nullptr); + JSON_ASSERT(m_type != value_t::binary || m_value.binary != nullptr); + } + + public: + ////////////////////////// + // JSON parser callback // + ////////////////////////// + + /*! + @brief parser event types + + The parser callback distinguishes the following events: + - `object_start`: the parser read `{` and started to process a JSON object + - `key`: the parser read a key of a value in an object + - `object_end`: the parser read `}` and finished processing a JSON object + - `array_start`: the parser read `[` and started to process a JSON array + - `array_end`: the parser read `]` and finished processing a JSON array + - `value`: the parser finished reading a JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + @sa @ref parser_callback_t for more information and examples + */ + using parse_event_t = detail::parse_event_t; + + /*! + @brief per-element parser callback type + + With a parser callback function, the result of parsing a JSON text can be + influenced. When passed to @ref parse, it is called on certain events + (passed as @ref parse_event_t via parameter @a event) with a set recursion + depth @a depth and context JSON value @a parsed. The return value of the + callback function is a boolean indicating whether the element that emitted + the callback shall be kept or not. + + We distinguish six scenarios (determined by the event type) in which the + callback function can be called. The following table describes the values + of the parameters @a depth, @a event, and @a parsed. + + parameter @a event | description | parameter @a depth | parameter @a parsed + ------------------ | ----------- | ------------------ | ------------------- + parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded + parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key + parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object + parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded + parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array + parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + Discarding a value (i.e., returning `false`) has different effects + depending on the context in which function was called: + + - Discarded values in structured types are skipped. That is, the parser + will behave as if the discarded value was never read. + - In case a value outside a structured type is skipped, it is replaced + with `null`. This case happens if the top-level element is skipped. + + @param[in] depth the depth of the recursion during parsing + + @param[in] event an event of type parse_event_t indicating the context in + the callback function has been called + + @param[in,out] parsed the current intermediate parse result; note that + writing to this value has no effect for parse_event_t::key events + + @return Whether the JSON value which called the function during parsing + should be kept (`true`) or not (`false`). In the latter case, it is either + skipped completely or replaced by an empty discarded object. + + @sa @ref parse for examples + + @since version 1.0.0 + */ + using parser_callback_t = detail::parser_callback_t; + + ////////////////// + // constructors // + ////////////////// + + /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. + /// @{ + + /*! + @brief create an empty value with a given type + + Create an empty JSON value with a given type. The value will be default + initialized with an empty value which depends on the type: + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + binary | empty array + + @param[in] v the type of the value to create + + @complexity Constant. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows the constructor for different @ref + value_t values,basic_json__value_t} + + @sa @ref clear() -- restores the postcondition of this constructor + + @since version 1.0.0 + */ + basic_json(const value_t v) + : m_type(v), m_value(v) + { + assert_invariant(); + } + + /*! + @brief create a null object + + Create a `null` JSON value. It either takes a null pointer as parameter + (explicitly creating `null`) or no parameter (implicitly creating `null`). + The passed null pointer itself is not read -- it is only used to choose + the right constructor. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @liveexample{The following code shows the constructor with and without a + null pointer parameter.,basic_json__nullptr_t} + + @since version 1.0.0 + */ + basic_json(std::nullptr_t = nullptr) noexcept + : basic_json(value_t::null) + { + assert_invariant(); + } + + /*! + @brief create a JSON value + + This is a "catch all" constructor for all compatible JSON types; that is, + types for which a `to_json()` method exists. The constructor forwards the + parameter @a val to that method (to `json_serializer::to_json` method + with `U = uncvref_t`, to be exact). + + Template type @a CompatibleType includes, but is not limited to, the + following types: + - **arrays**: @ref array_t and all kinds of compatible containers such as + `std::vector`, `std::deque`, `std::list`, `std::forward_list`, + `std::array`, `std::valarray`, `std::set`, `std::unordered_set`, + `std::multiset`, and `std::unordered_multiset` with a `value_type` from + which a @ref basic_json value can be constructed. + - **objects**: @ref object_t and all kinds of compatible associative + containers such as `std::map`, `std::unordered_map`, `std::multimap`, + and `std::unordered_multimap` with a `key_type` compatible to + @ref string_t and a `value_type` from which a @ref basic_json value can + be constructed. + - **strings**: @ref string_t, string literals, and all compatible string + containers can be used. + - **numbers**: @ref number_integer_t, @ref number_unsigned_t, + @ref number_float_t, and all convertible number types such as `int`, + `size_t`, `int64_t`, `float` or `double` can be used. + - **boolean**: @ref boolean_t / `bool` can be used. + - **binary**: @ref binary_t / `std::vector` may be used, + unfortunately because string literals cannot be distinguished from binary + character arrays by the C++ type system, all types compatible with `const + char*` will be directed to the string constructor instead. This is both + for backwards compatibility, and due to the fact that a binary type is not + a standard JSON type. + + See the examples below. + + @tparam CompatibleType a type such that: + - @a CompatibleType is not derived from `std::istream`, + - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move + constructors), + - @a CompatibleType is not a different @ref basic_json type (i.e. with different template arguments) + - @a CompatibleType is not a @ref basic_json nested type (e.g., + @ref json_pointer, @ref iterator, etc ...) + - @ref @ref json_serializer has a + `to_json(basic_json_t&, CompatibleType&&)` method + + @tparam U = `uncvref_t` + + @param[in] val the value to be forwarded to the respective constructor + + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. + + @exceptionsafety Depends on the called constructor. For types directly + supported by the library (i.e., all types for which no `to_json()` function + was provided), strong guarantee holds: if an exception is thrown, there are + no changes to any JSON value. + + @liveexample{The following code shows the constructor with several + compatible types.,basic_json__CompatibleType} + + @since version 2.1.0 + */ + template < typename CompatibleType, + typename U = detail::uncvref_t, + detail::enable_if_t < + !detail::is_basic_json::value && detail::is_compatible_type::value, int > = 0 > + basic_json(CompatibleType && val) noexcept(noexcept( + JSONSerializer::to_json(std::declval(), + std::forward(val)))) + { + JSONSerializer::to_json(*this, std::forward(val)); + assert_invariant(); + } + + /*! + @brief create a JSON value from an existing one + + This is a constructor for existing @ref basic_json types. + It does not hijack copy/move constructors, since the parameter has different + template arguments than the current ones. + + The constructor tries to convert the internal @ref m_value of the parameter. + + @tparam BasicJsonType a type such that: + - @a BasicJsonType is a @ref basic_json type. + - @a BasicJsonType has different template arguments than @ref basic_json_t. + + @param[in] val the @ref basic_json value to be converted. + + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. + + @exceptionsafety Depends on the called constructor. For types directly + supported by the library (i.e., all types for which no `to_json()` function + was provided), strong guarantee holds: if an exception is thrown, there are + no changes to any JSON value. + + @since version 3.2.0 + */ + template < typename BasicJsonType, + detail::enable_if_t < + detail::is_basic_json::value&& !std::is_same::value, int > = 0 > + basic_json(const BasicJsonType& val) + { + using other_boolean_t = typename BasicJsonType::boolean_t; + using other_number_float_t = typename BasicJsonType::number_float_t; + using other_number_integer_t = typename BasicJsonType::number_integer_t; + using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using other_string_t = typename BasicJsonType::string_t; + using other_object_t = typename BasicJsonType::object_t; + using other_array_t = typename BasicJsonType::array_t; + using other_binary_t = typename BasicJsonType::binary_t; + + switch (val.type()) + { + case value_t::boolean: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_float: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_integer: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_unsigned: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::string: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::object: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::array: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::binary: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::null: + *this = nullptr; + break; + case value_t::discarded: + m_type = value_t::discarded; + break; + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + assert_invariant(); + } + + /*! + @brief create a container (array or object) from an initializer list + + Creates a JSON value of type array or object from the passed initializer + list @a init. In case @a type_deduction is `true` (default), the type of + the JSON value to be created is deducted from the initializer list @a init + according to the following rules: + + 1. If the list is empty, an empty JSON object value `{}` is created. + 2. If the list consists of pairs whose first element is a string, a JSON + object value is created where the first elements of the pairs are + treated as keys and the second elements are as values. + 3. In all other cases, an array is created. + + The rules aim to create the best fit between a C++ initializer list and + JSON values. The rationale is as follows: + + 1. The empty initializer list is written as `{}` which is exactly an empty + JSON object. + 2. C++ has no way of describing mapped types other than to list a list of + pairs. As JSON requires that keys must be of type string, rule 2 is the + weakest constraint one can pose on initializer lists to interpret them + as an object. + 3. In all other cases, the initializer list could not be interpreted as + JSON object type, so interpreting it as JSON array type is safe. + + With the rules described above, the following JSON values cannot be + expressed by an initializer list: + + - the empty array (`[]`): use @ref array(initializer_list_t) + with an empty initializer list in this case + - arrays whose elements satisfy rule 2: use @ref + array(initializer_list_t) with the same initializer list + in this case + + @note When used without parentheses around an empty initializer list, @ref + basic_json() is called instead of this function, yielding the JSON null + value. + + @param[in] init initializer list with JSON values + + @param[in] type_deduction internal parameter; when set to `true`, the type + of the JSON value is deducted from the initializer list @a init; when set + to `false`, the type provided via @a manual_type is forced. This mode is + used by the functions @ref array(initializer_list_t) and + @ref object(initializer_list_t). + + @param[in] manual_type internal parameter; when @a type_deduction is set + to `false`, the created JSON value will use the provided type (only @ref + value_t::array and @ref value_t::object are valid); when @a type_deduction + is set to `true`, this parameter has no effect + + @throw type_error.301 if @a type_deduction is `false`, @a manual_type is + `value_t::object`, but @a init contains an element which is not a pair + whose first element is a string. In this case, the constructor could not + create an object. If @a type_deduction would have be `true`, an array + would have been created. See @ref object(initializer_list_t) + for an example. + + @complexity Linear in the size of the initializer list @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The example below shows how JSON values are created from + initializer lists.,basic_json__list_init_t} + + @sa @ref array(initializer_list_t) -- create a JSON array + value from an initializer list + @sa @ref object(initializer_list_t) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + basic_json(initializer_list_t init, + bool type_deduction = true, + value_t manual_type = value_t::array) + { + // check if each element is an array with two elements whose first + // element is a string + bool is_an_object = std::all_of(init.begin(), init.end(), + [](const detail::json_ref& element_ref) + { + return element_ref->is_array() && element_ref->size() == 2 && (*element_ref)[0].is_string(); + }); + + // adjust type if type deduction is not wanted + if (!type_deduction) + { + // if array is wanted, do not create an object though possible + if (manual_type == value_t::array) + { + is_an_object = false; + } + + // if object is wanted but impossible, throw an exception + if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object)) + { + JSON_THROW(type_error::create(301, "cannot create object from initializer list")); + } + } + + if (is_an_object) + { + // the initializer list is a list of pairs -> create object + m_type = value_t::object; + m_value = value_t::object; + + std::for_each(init.begin(), init.end(), [this](const detail::json_ref& element_ref) + { + auto element = element_ref.moved_or_copied(); + m_value.object->emplace( + std::move(*((*element.m_value.array)[0].m_value.string)), + std::move((*element.m_value.array)[1])); + }); + } + else + { + // the initializer list describes an array -> create array + m_type = value_t::array; + m_value.array = create(init.begin(), init.end()); + } + + assert_invariant(); + } + + /*! + @brief explicitly create a binary array (without subtype) + + Creates a JSON binary array value from a given binary container. Binary + values are part of various binary formats, such as CBOR, MessagePack, and + BSON. This constructor is used to create a value for serialization to those + formats. + + @note Note, this function exists because of the difficulty in correctly + specifying the correct template overload in the standard value ctor, as both + JSON arrays and JSON binary arrays are backed with some form of a + `std::vector`. Because JSON binary arrays are a non-standard extension it + was decided that it would be best to prevent automatic initialization of a + binary array type, for backwards compatibility and so it does not happen on + accident. + + @param[in] init container containing bytes to use as binary type + + @return JSON binary array value + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @since version 3.8.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(const typename binary_t::container_type& init) + { + auto res = basic_json(); + res.m_type = value_t::binary; + res.m_value = init; + return res; + } + + /*! + @brief explicitly create a binary array (with subtype) + + Creates a JSON binary array value from a given binary container. Binary + values are part of various binary formats, such as CBOR, MessagePack, and + BSON. This constructor is used to create a value for serialization to those + formats. + + @note Note, this function exists because of the difficulty in correctly + specifying the correct template overload in the standard value ctor, as both + JSON arrays and JSON binary arrays are backed with some form of a + `std::vector`. Because JSON binary arrays are a non-standard extension it + was decided that it would be best to prevent automatic initialization of a + binary array type, for backwards compatibility and so it does not happen on + accident. + + @param[in] init container containing bytes to use as binary type + @param[in] subtype subtype to use in MessagePack and BSON + + @return JSON binary array value + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @since version 3.8.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(const typename binary_t::container_type& init, std::uint8_t subtype) + { + auto res = basic_json(); + res.m_type = value_t::binary; + res.m_value = binary_t(init, subtype); + return res; + } + + /// @copydoc binary(const typename binary_t::container_type&) + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(typename binary_t::container_type&& init) + { + auto res = basic_json(); + res.m_type = value_t::binary; + res.m_value = std::move(init); + return res; + } + + /// @copydoc binary(const typename binary_t::container_type&, std::uint8_t) + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(typename binary_t::container_type&& init, std::uint8_t subtype) + { + auto res = basic_json(); + res.m_type = value_t::binary; + res.m_value = binary_t(std::move(init), subtype); + return res; + } + + /*! + @brief explicitly create an array from an initializer list + + Creates a JSON array value from a given initializer list. That is, given a + list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the + initializer list is empty, the empty array `[]` is created. + + @note This function is only needed to express two edge cases that cannot + be realized with the initializer list constructor (@ref + basic_json(initializer_list_t, bool, value_t)). These cases + are: + 1. creating an array whose elements are all pairs whose first element is a + string -- in this case, the initializer list constructor would create an + object, taking the first elements as keys + 2. creating an empty array -- passing the empty initializer list to the + initializer list constructor yields an empty object + + @param[in] init initializer list with JSON values to create an array from + (optional) + + @return JSON array value + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows an example for the `array` + function.,array} + + @sa @ref basic_json(initializer_list_t, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref object(initializer_list_t) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json array(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::array); + } + + /*! + @brief explicitly create an object from an initializer list + + Creates a JSON object value from a given initializer list. The initializer + lists elements must be pairs, and their first elements must be strings. If + the initializer list is empty, the empty object `{}` is created. + + @note This function is only added for symmetry reasons. In contrast to the + related function @ref array(initializer_list_t), there are + no cases which can only be expressed by this function. That is, any + initializer list @a init can also be passed to the initializer list + constructor @ref basic_json(initializer_list_t, bool, value_t). + + @param[in] init initializer list to create an object from (optional) + + @return JSON object value + + @throw type_error.301 if @a init is not a list of pairs whose first + elements are strings. In this case, no object can be created. When such a + value is passed to @ref basic_json(initializer_list_t, bool, value_t), + an array would have been created from the passed initializer list @a init. + See example below. + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows an example for the `object` + function.,object} + + @sa @ref basic_json(initializer_list_t, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref array(initializer_list_t) -- create a JSON array + value from an initializer list + + @since version 1.0.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json object(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::object); + } + + /*! + @brief construct an array with count copies of given value + + Constructs a JSON array value by creating @a cnt copies of a passed value. + In case @a cnt is `0`, an empty array is created. + + @param[in] cnt the number of JSON copies of @a val to create + @param[in] val the JSON value to copy + + @post `std::distance(begin(),end()) == cnt` holds. + + @complexity Linear in @a cnt. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows examples for the @ref + basic_json(size_type\, const basic_json&) + constructor.,basic_json__size_type_basic_json} + + @since version 1.0.0 + */ + basic_json(size_type cnt, const basic_json& val) + : m_type(value_t::array) + { + m_value.array = create(cnt, val); + assert_invariant(); + } + + /*! + @brief construct a JSON container given an iterator range + + Constructs the JSON value with the contents of the range `[first, last)`. + The semantics depends on the different types a JSON value can have: + - In case of a null type, invalid_iterator.206 is thrown. + - In case of other primitive types (number, boolean, or string), @a first + must be `begin()` and @a last must be `end()`. In this case, the value is + copied. Otherwise, invalid_iterator.204 is thrown. + - In case of structured types (array, object), the constructor behaves as + similar versions for `std::vector` or `std::map`; that is, a JSON array + or object is constructed from the values in the range. + + @tparam InputIT an input iterator type (@ref iterator or @ref + const_iterator) + + @param[in] first begin of the range to copy from (included) + @param[in] last end of the range to copy from (excluded) + + @pre Iterators @a first and @a last must be initialized. **This + precondition is enforced with an assertion (see warning).** If + assertions are switched off, a violation of this precondition yields + undefined behavior. + + @pre Range `[first, last)` is valid. Usually, this precondition cannot be + checked efficiently. Only certain edge cases are detected; see the + description of the exceptions below. A violation of this precondition + yields undefined behavior. + + @warning A precondition is enforced with a runtime assertion that will + result in calling `std::abort` if this precondition is not met. + Assertions can be disabled by defining `NDEBUG` at compile time. + See https://en.cppreference.com/w/cpp/error/assert for more + information. + + @throw invalid_iterator.201 if iterators @a first and @a last are not + compatible (i.e., do not belong to the same JSON value). In this case, + the range `[first, last)` is undefined. + @throw invalid_iterator.204 if iterators @a first and @a last belong to a + primitive type (number, boolean, or string), but @a first does not point + to the first element any more. In this case, the range `[first, last)` is + undefined. See example code below. + @throw invalid_iterator.206 if iterators @a first and @a last belong to a + null value. In this case, the range `[first, last)` is undefined. + + @complexity Linear in distance between @a first and @a last. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The example below shows several ways to create JSON values by + specifying a subrange with iterators.,basic_json__InputIt_InputIt} + + @since version 1.0.0 + */ + template < class InputIT, typename std::enable_if < + std::is_same::value || + std::is_same::value, int >::type = 0 > + basic_json(InputIT first, InputIT last) + { + JSON_ASSERT(first.m_object != nullptr); + JSON_ASSERT(last.m_object != nullptr); + + // make sure iterator fits the current value + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(201, "iterators are not compatible")); + } + + // copy type from first iterator + m_type = first.m_object->m_type; + + // check if iterator range is complete for primitive values + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() + || !last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } + break; + } + + default: + break; + } + + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = first.m_object->m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = first.m_object->m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value.number_float = first.m_object->m_value.number_float; + break; + } + + case value_t::boolean: + { + m_value.boolean = first.m_object->m_value.boolean; + break; + } + + case value_t::string: + { + m_value = *first.m_object->m_value.string; + break; + } + + case value_t::object: + { + m_value.object = create(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + m_value.array = create(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + case value_t::binary: + { + m_value = *first.m_object->m_value.binary; + break; + } + + default: + JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " + + std::string(first.m_object->type_name()))); + } + + assert_invariant(); + } + + + /////////////////////////////////////// + // other constructors and destructor // + /////////////////////////////////////// + + template, + std::is_same>::value, int> = 0 > + basic_json(const JsonRef& ref) : basic_json(ref.moved_or_copied()) {} + + /*! + @brief copy constructor + + Creates a copy of a given JSON value. + + @param[in] other the JSON value to copy + + @post `*this == other` + + @complexity Linear in the size of @a other. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is linear. + - As postcondition, it holds: `other == basic_json(other)`. + + @liveexample{The following code shows an example for the copy + constructor.,basic_json__basic_json} + + @since version 1.0.0 + */ + basic_json(const basic_json& other) + : m_type(other.m_type) + { + // check of passed value is valid + other.assert_invariant(); + + switch (m_type) + { + case value_t::object: + { + m_value = *other.m_value.object; + break; + } + + case value_t::array: + { + m_value = *other.m_value.array; + break; + } + + case value_t::string: + { + m_value = *other.m_value.string; + break; + } + + case value_t::boolean: + { + m_value = other.m_value.boolean; + break; + } + + case value_t::number_integer: + { + m_value = other.m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value = other.m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value = other.m_value.number_float; + break; + } + + case value_t::binary: + { + m_value = *other.m_value.binary; + break; + } + + default: + break; + } + + assert_invariant(); + } + + /*! + @brief move constructor + + Move constructor. Constructs a JSON value with the contents of the given + value @a other using move semantics. It "steals" the resources from @a + other and leaves it as JSON null value. + + @param[in,out] other value to move to this object + + @post `*this` has the same value as @a other before the call. + @post @a other is a JSON null value. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @requirement This function helps `basic_json` satisfying the + [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible) + requirements. + + @liveexample{The code below shows the move constructor explicitly called + via std::move.,basic_json__moveconstructor} + + @since version 1.0.0 + */ + basic_json(basic_json&& other) noexcept + : m_type(std::move(other.m_type)), + m_value(std::move(other.m_value)) + { + // check that passed value is valid + other.assert_invariant(); + + // invalidate payload + other.m_type = value_t::null; + other.m_value = {}; + + assert_invariant(); + } + + /*! + @brief copy assignment + + Copy assignment operator. Copies a JSON value via the "copy and swap" + strategy: It is expressed in terms of the copy constructor, destructor, + and the `swap()` member function. + + @param[in] other value to copy from + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is linear. + + @liveexample{The code below shows and example for the copy assignment. It + creates a copy of value `a` which is then swapped with `b`. Finally\, the + copy of `a` (which is the null value after the swap) is + destroyed.,basic_json__copyassignment} + + @since version 1.0.0 + */ + basic_json& operator=(basic_json other) noexcept ( + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value&& + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value + ) + { + // check that passed value is valid + other.assert_invariant(); + + using std::swap; + swap(m_type, other.m_type); + swap(m_value, other.m_value); + + assert_invariant(); + return *this; + } + + /*! + @brief destructor + + Destroys the JSON value and frees all allocated memory. + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is linear. + - All stored elements are destroyed and all memory is freed. + + @since version 1.0.0 + */ + ~basic_json() noexcept + { + assert_invariant(); + m_value.destroy(m_type); + } + + /// @} + + public: + /////////////////////// + // object inspection // + /////////////////////// + + /// @name object inspection + /// Functions to inspect the type of a JSON value. + /// @{ + + /*! + @brief serialization + + Serialization function for JSON values. The function tries to mimic + Python's `json.dumps()` function, and currently supports its @a indent + and @a ensure_ascii parameters. + + @param[in] indent If indent is nonnegative, then array elements and object + members will be pretty-printed with that indent level. An indent level of + `0` will only insert newlines. `-1` (the default) selects the most compact + representation. + @param[in] indent_char The character to use for indentation if @a indent is + greater than `0`. The default is ` ` (space). + @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters + in the output are escaped with `\uXXXX` sequences, and the result consists + of ASCII characters only. + @param[in] error_handler how to react on decoding errors; there are three + possible values: `strict` (throws and exception in case a decoding error + occurs; default), `replace` (replace invalid UTF-8 sequences with U+FFFD), + and `ignore` (ignore invalid UTF-8 sequences during serialization; all + bytes are copied to the output unchanged). + + @return string containing the serialization of the JSON value + + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded and @a error_handler is set to strict + + @note Binary values are serialized as object containing two keys: + - "bytes": an array of bytes as integers + - "subtype": the subtype as integer or "null" if the binary has no subtype + + @complexity Linear. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @liveexample{The following example shows the effect of different @a indent\, + @a indent_char\, and @a ensure_ascii parameters to the result of the + serialization.,dump} + + @see https://docs.python.org/2/library/json.html#json.dump + + @since version 1.0.0; indentation character @a indent_char, option + @a ensure_ascii and exceptions added in version 3.0.0; error + handlers added in version 3.4.0; serialization of binary values added + in version 3.8.0. + */ + string_t dump(const int indent = -1, + const char indent_char = ' ', + const bool ensure_ascii = false, + const error_handler_t error_handler = error_handler_t::strict) const + { + string_t result; + serializer s(detail::output_adapter(result), indent_char, error_handler); + + if (indent >= 0) + { + s.dump(*this, true, ensure_ascii, static_cast(indent)); + } + else + { + s.dump(*this, false, ensure_ascii, 0); + } + + return result; + } + + /*! + @brief return the type of the JSON value (explicit) + + Return the type of the JSON value as a value from the @ref value_t + enumeration. + + @return the type of the JSON value + Value type | return value + ------------------------- | ------------------------- + null | value_t::null + boolean | value_t::boolean + string | value_t::string + number (integer) | value_t::number_integer + number (unsigned integer) | value_t::number_unsigned + number (floating-point) | value_t::number_float + object | value_t::object + array | value_t::array + binary | value_t::binary + discarded | value_t::discarded + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `type()` for all JSON + types.,type} + + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + @sa @ref type_name() -- return the type as string + + @since version 1.0.0 + */ + constexpr value_t type() const noexcept + { + return m_type; + } + + /*! + @brief return whether type is primitive + + This function returns true if and only if the JSON type is primitive + (string, number, boolean, or null). + + @return `true` if type is primitive (string, number, boolean, or null), + `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_primitive()` for all JSON + types.,is_primitive} + + @sa @ref is_structured() -- returns whether JSON value is structured + @sa @ref is_null() -- returns whether JSON value is `null` + @sa @ref is_string() -- returns whether JSON value is a string + @sa @ref is_boolean() -- returns whether JSON value is a boolean + @sa @ref is_number() -- returns whether JSON value is a number + @sa @ref is_binary() -- returns whether JSON value is a binary array + + @since version 1.0.0 + */ + constexpr bool is_primitive() const noexcept + { + return is_null() || is_string() || is_boolean() || is_number() || is_binary(); + } + + /*! + @brief return whether type is structured + + This function returns true if and only if the JSON type is structured + (array or object). + + @return `true` if type is structured (array or object), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_structured()` for all JSON + types.,is_structured} + + @sa @ref is_primitive() -- returns whether value is primitive + @sa @ref is_array() -- returns whether value is an array + @sa @ref is_object() -- returns whether value is an object + + @since version 1.0.0 + */ + constexpr bool is_structured() const noexcept + { + return is_array() || is_object(); + } + + /*! + @brief return whether value is null + + This function returns true if and only if the JSON value is null. + + @return `true` if type is null, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_null()` for all JSON + types.,is_null} + + @since version 1.0.0 + */ + constexpr bool is_null() const noexcept + { + return m_type == value_t::null; + } + + /*! + @brief return whether value is a boolean + + This function returns true if and only if the JSON value is a boolean. + + @return `true` if type is boolean, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_boolean()` for all JSON + types.,is_boolean} + + @since version 1.0.0 + */ + constexpr bool is_boolean() const noexcept + { + return m_type == value_t::boolean; + } + + /*! + @brief return whether value is a number + + This function returns true if and only if the JSON value is a number. This + includes both integer (signed and unsigned) and floating-point values. + + @return `true` if type is number (regardless whether integer, unsigned + integer or floating-type), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number()` for all JSON + types.,is_number} + + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number() const noexcept + { + return is_number_integer() || is_number_float(); + } + + /*! + @brief return whether value is an integer number + + This function returns true if and only if the JSON value is a signed or + unsigned integer number. This excludes floating-point values. + + @return `true` if type is an integer or unsigned integer number, `false` + otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_integer()` for all + JSON types.,is_number_integer} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number_integer() const noexcept + { + return m_type == value_t::number_integer || m_type == value_t::number_unsigned; + } + + /*! + @brief return whether value is an unsigned integer number + + This function returns true if and only if the JSON value is an unsigned + integer number. This excludes floating-point and signed integer values. + + @return `true` if type is an unsigned integer number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_unsigned()` for all + JSON types.,is_number_unsigned} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 2.0.0 + */ + constexpr bool is_number_unsigned() const noexcept + { + return m_type == value_t::number_unsigned; + } + + /*! + @brief return whether value is a floating-point number + + This function returns true if and only if the JSON value is a + floating-point number. This excludes signed and unsigned integer values. + + @return `true` if type is a floating-point number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_float()` for all + JSON types.,is_number_float} + + @sa @ref is_number() -- check if value is number + @sa @ref is_number_integer() -- check if value is an integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + + @since version 1.0.0 + */ + constexpr bool is_number_float() const noexcept + { + return m_type == value_t::number_float; + } + + /*! + @brief return whether value is an object + + This function returns true if and only if the JSON value is an object. + + @return `true` if type is object, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_object()` for all JSON + types.,is_object} + + @since version 1.0.0 + */ + constexpr bool is_object() const noexcept + { + return m_type == value_t::object; + } + + /*! + @brief return whether value is an array + + This function returns true if and only if the JSON value is an array. + + @return `true` if type is array, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_array()` for all JSON + types.,is_array} + + @since version 1.0.0 + */ + constexpr bool is_array() const noexcept + { + return m_type == value_t::array; + } + + /*! + @brief return whether value is a string + + This function returns true if and only if the JSON value is a string. + + @return `true` if type is string, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_string()` for all JSON + types.,is_string} + + @since version 1.0.0 + */ + constexpr bool is_string() const noexcept + { + return m_type == value_t::string; + } + + /*! + @brief return whether value is a binary array + + This function returns true if and only if the JSON value is a binary array. + + @return `true` if type is binary array, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_binary()` for all JSON + types.,is_binary} + + @since version 3.8.0 + */ + constexpr bool is_binary() const noexcept + { + return m_type == value_t::binary; + } + + /*! + @brief return whether value is discarded + + This function returns true if and only if the JSON value was discarded + during parsing with a callback function (see @ref parser_callback_t). + + @note This function will always be `false` for JSON values after parsing. + That is, discarded values can only occur during parsing, but will be + removed when inside a structured value or replaced by null in other cases. + + @return `true` if type is discarded, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_discarded()` for all JSON + types.,is_discarded} + + @since version 1.0.0 + */ + constexpr bool is_discarded() const noexcept + { + return m_type == value_t::discarded; + } + + /*! + @brief return the type of the JSON value (implicit) + + Implicitly return the type of the JSON value as a value from the @ref + value_t enumeration. + + @return the type of the JSON value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies the @ref value_t operator for + all JSON types.,operator__value_t} + + @sa @ref type() -- return the type of the JSON value (explicit) + @sa @ref type_name() -- return the type as string + + @since version 1.0.0 + */ + constexpr operator value_t() const noexcept + { + return m_type; + } + + /// @} + + private: + ////////////////// + // value access // + ////////////////// + + /// get a boolean (explicit) + boolean_t get_impl(boolean_t* /*unused*/) const + { + if (JSON_HEDLEY_LIKELY(is_boolean())) + { + return m_value.boolean; + } + + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(type_name()))); + } + + /// get a pointer to the value (object) + object_t* get_impl_ptr(object_t* /*unused*/) noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (object) + constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (array) + array_t* get_impl_ptr(array_t* /*unused*/) noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (array) + constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (string) + string_t* get_impl_ptr(string_t* /*unused*/) noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (string) + constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (boolean) + boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (boolean) + constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (integer number) + number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (integer number) + constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (unsigned number) + number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (unsigned number) + constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (floating-point number) + number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /// get a pointer to the value (floating-point number) + constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /// get a pointer to the value (binary) + binary_t* get_impl_ptr(binary_t* /*unused*/) noexcept + { + return is_binary() ? m_value.binary : nullptr; + } + + /// get a pointer to the value (binary) + constexpr const binary_t* get_impl_ptr(const binary_t* /*unused*/) const noexcept + { + return is_binary() ? m_value.binary : nullptr; + } + + /*! + @brief helper function to implement get_ref() + + This function helps to implement get_ref() without code duplication for + const and non-const overloads + + @tparam ThisType will be deduced as `basic_json` or `const basic_json` + + @throw type_error.303 if ReferenceType does not match underlying value + type of the current JSON + */ + template + static ReferenceType get_ref_impl(ThisType& obj) + { + // delegate the call to get_ptr<>() + auto ptr = obj.template get_ptr::type>(); + + if (JSON_HEDLEY_LIKELY(ptr != nullptr)) + { + return *ptr; + } + + JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + std::string(obj.type_name()))); + } + + public: + /// @name value access + /// Direct access to the stored value of a JSON value. + /// @{ + + /*! + @brief get special-case overload + + This overloads avoids a lot of template boilerplate, it can be seen as the + identity method + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this + + @complexity Constant. + + @since version 2.1.0 + */ + template::type, basic_json_t>::value, + int> = 0> + basic_json get() const + { + return *this; + } + + /*! + @brief get special-case overload + + This overloads converts the current @ref basic_json in a different + @ref basic_json type + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this, converted into @tparam BasicJsonType + + @complexity Depending on the implementation of the called `from_json()` + method. + + @since version 3.2.0 + */ + template < typename BasicJsonType, detail::enable_if_t < + !std::is_same::value&& + detail::is_basic_json::value, int > = 0 > + BasicJsonType get() const + { + return *this; + } + + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value + which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) + and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json, + - @ref json_serializer has a `from_json()` method of the form + `void from_json(const basic_json&, ValueType&)`, and + - @ref json_serializer does not have a `from_json()` method of + the form `ValueType from_json(const basic_json&)` + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,get__ValueType_const} + + @since version 2.1.0 + */ + template < typename ValueTypeCV, typename ValueType = detail::uncvref_t, + detail::enable_if_t < + !detail::is_basic_json::value && + detail::has_from_json::value && + !detail::has_non_default_from_json::value, + int > = 0 > + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval(), std::declval()))) + { + // we cannot static_assert on ValueTypeCV being non-const, because + // there is support for get(), which is why we + // still need the uncvref + static_assert(!std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + static_assert(std::is_default_constructible::value, + "types must be DefaultConstructible when used with get()"); + + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + } + + /*! + @brief get a value (explicit); special case + + Explicit type conversion between the JSON value and a compatible value + which is **not** [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) + and **not** [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + return JSONSerializer::from_json(*this); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json and + - @ref json_serializer has a `from_json()` method of the form + `ValueType from_json(const basic_json&)` + + @note If @ref json_serializer has both overloads of + `from_json()`, this one is chosen. + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @since version 2.1.0 + */ + template < typename ValueTypeCV, typename ValueType = detail::uncvref_t, + detail::enable_if_t < !std::is_same::value && + detail::has_non_default_from_json::value, + int > = 0 > + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval()))) + { + static_assert(!std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + return JSONSerializer::from_json(*this); + } + + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value. + The value is filled into the input parameter by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + ValueType v; + JSONSerializer::from_json(*this, v); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json, + - @ref json_serializer has a `from_json()` method of the form + `void from_json(const basic_json&, ValueType&)`, and + + @tparam ValueType the input parameter type. + + @return the input parameter, allowing chaining calls. + + @throw what @ref json_serializer `from_json()` method throws + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,get_to} + + @since version 3.3.0 + */ + template < typename ValueType, + detail::enable_if_t < + !detail::is_basic_json::value&& + detail::has_from_json::value, + int > = 0 > + ValueType & get_to(ValueType& v) const noexcept(noexcept( + JSONSerializer::from_json(std::declval(), v))) + { + JSONSerializer::from_json(*this, v); + return v; + } + + // specialization to allow to call get_to with a basic_json value + // see https://github.com/nlohmann/json/issues/2175 + template::value, + int> = 0> + ValueType & get_to(ValueType& v) const + { + v = *this; + return v; + } + + template < + typename T, std::size_t N, + typename Array = T (&)[N], + detail::enable_if_t < + detail::has_from_json::value, int > = 0 > + Array get_to(T (&v)[N]) const + noexcept(noexcept(JSONSerializer::from_json( + std::declval(), v))) + { + JSONSerializer::from_json(*this, v); + return v; + } + + + /*! + @brief get a pointer value (implicit) + + Implicit pointer access to the internally stored JSON value. No copies are + made. + + @warning Writing data to the pointee of the result yields an undefined + state. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. Enforced by a static + assertion. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get_ptr} + + @since version 1.0.0 + */ + template::value, int>::type = 0> + auto get_ptr() noexcept -> decltype(std::declval().get_impl_ptr(std::declval())) + { + // delegate the call to get_impl_ptr<>() + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a pointer value (implicit) + @copydoc get_ptr() + */ + template < typename PointerType, typename std::enable_if < + std::is_pointer::value&& + std::is_const::type>::value, int >::type = 0 > + constexpr auto get_ptr() const noexcept -> decltype(std::declval().get_impl_ptr(std::declval())) + { + // delegate the call to get_impl_ptr<>() const + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a pointer value (explicit) + + Explicit pointer access to the internally stored JSON value. No copies are + made. + + @warning The pointer becomes invalid if the underlying JSON object + changes. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get__PointerType} + + @sa @ref get_ptr() for explicit pointer-member access + + @since version 1.0.0 + */ + template::value, int>::type = 0> + auto get() noexcept -> decltype(std::declval().template get_ptr()) + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (explicit) + @copydoc get() + */ + template::value, int>::type = 0> + constexpr auto get() const noexcept -> decltype(std::declval().template get_ptr()) + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a reference value (implicit) + + Implicit reference access to the internally stored JSON value. No copies + are made. + + @warning Writing data to the referee of the result yields an undefined + state. + + @tparam ReferenceType reference type; must be a reference to @ref array_t, + @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or + @ref number_float_t. Enforced by static assertion. + + @return reference to the internally stored JSON value if the requested + reference type @a ReferenceType fits to the JSON value; throws + type_error.303 otherwise + + @throw type_error.303 in case passed type @a ReferenceType is incompatible + with the stored JSON value; see example below + + @complexity Constant. + + @liveexample{The example shows several calls to `get_ref()`.,get_ref} + + @since version 1.1.0 + */ + template::value, int>::type = 0> + ReferenceType get_ref() + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a reference value (implicit) + @copydoc get_ref() + */ + template < typename ReferenceType, typename std::enable_if < + std::is_reference::value&& + std::is_const::type>::value, int >::type = 0 > + ReferenceType get_ref() const + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a value (implicit) + + Implicit type conversion between the JSON value and a compatible value. + The call is realized by calling @ref get() const. + + @tparam ValueType non-pointer type compatible to the JSON value, for + instance `int` for JSON integer numbers, `bool` for JSON booleans, or + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. + + @return copy of the JSON value, converted to type @a ValueType + + @throw type_error.302 in case passed type @a ValueType is incompatible + to the JSON value type (e.g., the JSON value is of type boolean, but a + string is requested); see example below + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,operator__ValueType} + + @since version 1.0.0 + */ + template < typename ValueType, typename std::enable_if < + !std::is_pointer::value&& + !std::is_same>::value&& + !std::is_same::value&& + !detail::is_basic_json::value + && !std::is_same>::value +#if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914)) + && !std::is_same::value +#endif + && detail::is_detected::value + , int >::type = 0 > + JSON_EXPLICIT operator ValueType() const + { + // delegate the call to get<>() const + return get(); + } + + /*! + @return reference to the binary value + + @throw type_error.302 if the value is not binary + + @sa @ref is_binary() to check if the value is binary + + @since version 3.8.0 + */ + binary_t& get_binary() + { + if (!is_binary()) + { + JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(type_name()))); + } + + return *get_ptr(); + } + + /// @copydoc get_binary() + const binary_t& get_binary() const + { + if (!is_binary()) + { + JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(type_name()))); + } + + return *get_ptr(); + } + + /// @} + + + //////////////////// + // element access // + //////////////////// + + /// @name element access + /// Access to the JSON value. + /// @{ + + /*! + @brief access specified array element with bounds checking + + Returns a reference to the element at specified location @a idx, with + bounds checking. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__size_type} + */ + reference at(size_type idx) + { + // at only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + JSON_TRY + { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified array element with bounds checking + + Returns a const reference to the element at specified location @a idx, + with bounds checking. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__size_type_const} + */ + const_reference at(size_type idx) const + { + // at only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + JSON_TRY + { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a reference to the element at with specified key @a key, with + bounds checking. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Logarithmic in the size of the container. + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__object_t_key_type} + */ + reference at(const typename object_t::key_type& key) + { + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + JSON_TRY + { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a const reference to the element at with specified key @a key, + with bounds checking. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Logarithmic in the size of the container. + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__object_t_key_type_const} + */ + const_reference at(const typename object_t::key_type& key) const + { + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + JSON_TRY + { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified array element + + Returns a reference to the element at specified location @a idx. + + @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), + then the array is silently filled up with `null` values to make `idx` a + valid reference to the last stored element. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw type_error.305 if the JSON value is not an array or null; in that + cases, using the [] operator with an index makes no sense. + + @complexity Constant if @a idx is in the range of the array. Otherwise + linear in `idx - size()`. + + @liveexample{The example below shows how array elements can be read and + written using `[]` operator. Note the addition of `null` + values.,operatorarray__size_type} + + @since version 1.0.0 + */ + reference operator[](size_type idx) + { + // implicitly convert null value to an empty array + if (is_null()) + { + m_type = value_t::array; + m_value.array = create(); + assert_invariant(); + } + + // operator[] only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + // fill up array with null values if given idx is outside range + if (idx >= m_value.array->size()) + { + m_value.array->insert(m_value.array->end(), + idx - m_value.array->size() + 1, + basic_json()); + } + + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name()))); + } + + /*! + @brief access specified array element + + Returns a const reference to the element at specified location @a idx. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw type_error.305 if the JSON value is not an array; in that case, + using the [] operator with an index makes no sense. + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + the `[]` operator.,operatorarray__size_type_const} + + @since version 1.0.0 + */ + const_reference operator[](size_type idx) const + { + // const operator[] only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name()))); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + reference operator[](const typename object_t::key_type& key) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + // operator[] only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw type_error.305 if the JSON value is not an object; in that case, + using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + const_reference operator[](const typename object_t::key_type& key) const + { + // const operator[] only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + JSON_ASSERT(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + JSON_HEDLEY_NON_NULL(2) + reference operator[](T* key) + { + // implicitly convert null to object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw type_error.305 if the JSON value is not an object; in that case, + using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + JSON_HEDLEY_NON_NULL(2) + const_reference operator[](T* key) const + { + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + JSON_ASSERT(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); + } + + /*! + @brief access specified object element with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(key); + } catch(out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const typename object_t::key_type&), this function + does not throw if the given key @a key was not found. + + @note Unlike @ref operator[](const typename object_t::key_type& key), this + function does not implicitly add an element to the position defined by @a + key. This function is furthermore also applicable to const objects. + + @param[in] key key of the element to access + @param[in] default_value the value to return if @a key is not found + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw type_error.302 if @a default_value does not match the type of the + value at @a key + @throw type_error.306 if the JSON value is not an object; in that case, + using `value()` with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + + @since version 1.0.0 + */ + // using std::is_convertible in a std::enable_if will fail when using explicit conversions + template < class ValueType, typename std::enable_if < + detail::is_getable::value + && !std::is_same::value, int >::type = 0 > + ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const + { + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return it->template get(); + } + + return default_value; + } + + JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const typename object_t::key_type&, const ValueType&) const + */ + string_t value(const typename object_t::key_type& key, const char* default_value) const + { + return value(key, string_t(default_value)); + } + + /*! + @brief access specified object element via JSON Pointer with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(ptr); + } catch(out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const json_pointer&), this function does not throw + if the given key @a key was not found. + + @param[in] ptr a JSON pointer to the element to access + @param[in] default_value the value to return if @a ptr found no value + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw type_error.302 if @a default_value does not match the type of the + value at @a ptr + @throw type_error.306 if the JSON value is not an object; in that case, + using `value()` with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value_ptr} + + @sa @ref operator[](const json_pointer&) for unchecked access by reference + + @since version 2.0.2 + */ + template::value, int>::type = 0> + ValueType value(const json_pointer& ptr, const ValueType& default_value) const + { + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // if pointer resolves a value, return it or use default value + JSON_TRY + { + return ptr.get_checked(this).template get(); + } + JSON_INTERNAL_CATCH (out_of_range&) + { + return default_value; + } + } + + JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const json_pointer&, ValueType) const + */ + JSON_HEDLEY_NON_NULL(3) + string_t value(const json_pointer& ptr, const char* default_value) const + { + return value(ptr, string_t(default_value)); + } + + /*! + @brief access the first element + + Returns a reference to the first element in the container. For a JSON + container `c`, the expression `c.front()` is equivalent to `*c.begin()`. + + @return In case of a structured type (array or object), a reference to the + first element is returned. In case of number, string, boolean, or binary + values, a reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw invalid_iterator.214 when called on `null` value + + @liveexample{The following code shows an example for `front()`.,front} + + @sa @ref back() -- access the last element + + @since version 1.0.0 + */ + reference front() + { + return *begin(); + } + + /*! + @copydoc basic_json::front() + */ + const_reference front() const + { + return *cbegin(); + } + + /*! + @brief access the last element + + Returns a reference to the last element in the container. For a JSON + container `c`, the expression `c.back()` is equivalent to + @code {.cpp} + auto tmp = c.end(); + --tmp; + return *tmp; + @endcode + + @return In case of a structured type (array or object), a reference to the + last element is returned. In case of number, string, boolean, or binary + values, a reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw invalid_iterator.214 when called on a `null` value. See example + below. + + @liveexample{The following code shows an example for `back()`.,back} + + @sa @ref front() -- access the first element + + @since version 1.0.0 + */ + reference back() + { + auto tmp = end(); + --tmp; + return *tmp; + } + + /*! + @copydoc basic_json::back() + */ + const_reference back() const + { + auto tmp = cend(); + --tmp; + return *tmp; + } + + /*! + @brief remove element given an iterator + + Removes the element specified by iterator @a pos. The iterator @a pos must + be valid and dereferenceable. Thus the `end()` iterator (which is valid, + but is not dereferenceable) cannot be used as a value for @a pos. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] pos iterator to the element to remove + @return Iterator following the last removed element. If the iterator @a + pos refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.202 if called on an iterator which does not belong + to the current JSON value; example: `"iterator does not fit current + value"` + @throw invalid_iterator.205 if called on a primitive type with invalid + iterator (i.e., any iterator which is not `begin()`); example: `"iterator + out of range"` + + @complexity The complexity depends on the type: + - objects: amortized constant + - arrays: linear in distance between @a pos and the end of the container + - strings and binary: linear in the length of the member + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType} + + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template < class IteratorType, typename std::enable_if < + std::is_same::value || + std::is_same::value, int >::type + = 0 > + IteratorType erase(IteratorType pos) + { + // make sure iterator fits the current value + if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + case value_t::binary: + { + if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin())) + { + JSON_THROW(invalid_iterator::create(205, "iterator out of range")); + } + + if (is_string()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.string); + std::allocator_traits::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } + else if (is_binary()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.binary); + std::allocator_traits::deallocate(alloc, m_value.binary, 1); + m_value.binary = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); + break; + } + + default: + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + return result; + } + + /*! + @brief remove elements given an iterator range + + Removes the element specified by the range `[first; last)`. The iterator + @a first does not need to be dereferenceable if `first == last`: erasing + an empty range is a no-op. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] first iterator to the beginning of the range to remove + @param[in] last iterator past the end of the range to remove + @return Iterator following the last removed element. If the iterator @a + second refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.203 if called on iterators which does not belong + to the current JSON value; example: `"iterators do not fit current value"` + @throw invalid_iterator.204 if called on a primitive type with invalid + iterators (i.e., if `first != begin()` and `last != end()`); example: + `"iterators out of range"` + + @complexity The complexity depends on the type: + - objects: `log(size()) + std::distance(first, last)` + - arrays: linear in the distance between @a first and @a last, plus linear + in the distance between @a last and end of the container + - strings and binary: linear in the length of the member + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType_IteratorType} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template < class IteratorType, typename std::enable_if < + std::is_same::value || + std::is_same::value, int >::type + = 0 > + IteratorType erase(IteratorType first, IteratorType last) + { + // make sure iterator fits the current value + if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) + { + JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value")); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + case value_t::binary: + { + if (JSON_HEDLEY_LIKELY(!first.m_it.primitive_iterator.is_begin() + || !last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } + + if (is_string()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.string); + std::allocator_traits::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } + else if (is_binary()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.binary); + std::allocator_traits::deallocate(alloc, m_value.binary, 1); + m_value.binary = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + return result; + } + + /*! + @brief remove element from a JSON object given a key + + Removes elements from a JSON object with the key value @a key. + + @param[in] key value of the elements to remove + + @return Number of elements removed. If @a ObjectType is the default + `std::map` type, the return value will always be `0` (@a key was not + found) or `1` (@a key was found). + + @post References and iterators to the erased elements are invalidated. + Other references and iterators are not affected. + + @throw type_error.307 when called on a type other than JSON object; + example: `"cannot use erase() with null"` + + @complexity `log(size()) + count(key)` + + @liveexample{The example shows the effect of `erase()`.,erase__key_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + size_type erase(const typename object_t::key_type& key) + { + // this erase only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + return m_value.object->erase(key); + } + + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + /*! + @brief remove element from a JSON array given an index + + Removes element from a JSON array at the index @a idx. + + @param[in] idx index of the element to remove + + @throw type_error.307 when called on a type other than JSON object; + example: `"cannot use erase() with null"` + @throw out_of_range.401 when `idx >= size()`; example: `"array index 17 + is out of range"` + + @complexity Linear in distance between @a idx and the end of the container. + + @liveexample{The example shows the effect of `erase()`.,erase__size_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + + @since version 1.0.0 + */ + void erase(const size_type idx) + { + // this erase only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + if (JSON_HEDLEY_UNLIKELY(idx >= size())) + { + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + + m_value.array->erase(m_value.array->begin() + static_cast(idx)); + } + else + { + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + } + + /// @} + + + //////////// + // lookup // + //////////// + + /// @name lookup + /// @{ + + /*! + @brief find an element in a JSON object + + Finds an element in a JSON object with key equivalent to @a key. If the + element is not found or the JSON value is not an object, end() is + returned. + + @note This method always returns @ref end() when executed on a JSON type + that is not an object. + + @param[in] key key value of the element to search for. + + @return Iterator to an element with key equivalent to @a key. If no such + element is found or the JSON value is not an object, past-the-end (see + @ref end()) iterator is returned. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `find()` is used.,find__key_type} + + @sa @ref contains(KeyT&&) const -- checks whether a key exists + + @since version 1.0.0 + */ + template + iterator find(KeyT&& key) + { + auto result = end(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(std::forward(key)); + } + + return result; + } + + /*! + @brief find an element in a JSON object + @copydoc find(KeyT&&) + */ + template + const_iterator find(KeyT&& key) const + { + auto result = cend(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(std::forward(key)); + } + + return result; + } + + /*! + @brief returns the number of occurrences of a key in a JSON object + + Returns the number of elements with key @a key. If ObjectType is the + default `std::map` type, the return value will always be `0` (@a key was + not found) or `1` (@a key was found). + + @note This method always returns `0` when executed on a JSON type that is + not an object. + + @param[in] key key value of the element to count + + @return Number of elements with key @a key. If the JSON value is not an + object, the return value will be `0`. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `count()` is used.,count} + + @since version 1.0.0 + */ + template + size_type count(KeyT&& key) const + { + // return 0 for all nonobject types + return is_object() ? m_value.object->count(std::forward(key)) : 0; + } + + /*! + @brief check the existence of an element in a JSON object + + Check whether an element exists in a JSON object with key equivalent to + @a key. If the element is not found or the JSON value is not an object, + false is returned. + + @note This method always returns false when executed on a JSON type + that is not an object. + + @param[in] key key value to check its existence. + + @return true if an element with specified @a key exists. If no such + element with such key is found or the JSON value is not an object, + false is returned. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The following code shows an example for `contains()`.,contains} + + @sa @ref find(KeyT&&) -- returns an iterator to an object element + @sa @ref contains(const json_pointer&) const -- checks the existence for a JSON pointer + + @since version 3.6.0 + */ + template < typename KeyT, typename std::enable_if < + !std::is_same::type, json_pointer>::value, int >::type = 0 > + bool contains(KeyT && key) const + { + return is_object() && m_value.object->find(std::forward(key)) != m_value.object->end(); + } + + /*! + @brief check the existence of an element in a JSON object given a JSON pointer + + Check whether the given JSON pointer @a ptr can be resolved in the current + JSON value. + + @note This method can be executed on any JSON value type. + + @param[in] ptr JSON pointer to check its existence. + + @return true if the JSON pointer can be resolved to a stored value, false + otherwise. + + @post If `j.contains(ptr)` returns true, it is safe to call `j[ptr]`. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The following code shows an example for `contains()`.,contains_json_pointer} + + @sa @ref contains(KeyT &&) const -- checks the existence of a key + + @since version 3.7.0 + */ + bool contains(const json_pointer& ptr) const + { + return ptr.contains(this); + } + + /// @} + + + /////////////// + // iterators // + /////////////// + + /// @name iterators + /// @{ + + /*! + @brief returns an iterator to the first element + + Returns an iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `begin()`.,begin} + + @sa @ref cbegin() -- returns a const iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + iterator begin() noexcept + { + iterator result(this); + result.set_begin(); + return result; + } + + /*! + @copydoc basic_json::cbegin() + */ + const_iterator begin() const noexcept + { + return cbegin(); + } + + /*! + @brief returns a const iterator to the first element + + Returns a const iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).begin()`. + + @liveexample{The following code shows an example for `cbegin()`.,cbegin} + + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + const_iterator cbegin() const noexcept + { + const_iterator result(this); + result.set_begin(); + return result; + } + + /*! + @brief returns an iterator to one past the last element + + Returns an iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `end()`.,end} + + @sa @ref cend() -- returns a const iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + iterator end() noexcept + { + iterator result(this); + result.set_end(); + return result; + } + + /*! + @copydoc basic_json::cend() + */ + const_iterator end() const noexcept + { + return cend(); + } + + /*! + @brief returns a const iterator to one past the last element + + Returns a const iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).end()`. + + @liveexample{The following code shows an example for `cend()`.,cend} + + @sa @ref end() -- returns an iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + const_iterator cend() const noexcept + { + const_iterator result(this); + result.set_end(); + return result; + } + + /*! + @brief returns an iterator to the reverse-beginning + + Returns an iterator to the reverse-beginning; that is, the last element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(end())`. + + @liveexample{The following code shows an example for `rbegin()`.,rbegin} + + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + /*! + @copydoc basic_json::crbegin() + */ + const_reverse_iterator rbegin() const noexcept + { + return crbegin(); + } + + /*! + @brief returns an iterator to the reverse-end + + Returns an iterator to the reverse-end; that is, one before the first + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(begin())`. + + @liveexample{The following code shows an example for `rend()`.,rend} + + @sa @ref crend() -- returns a const reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + /*! + @copydoc basic_json::crend() + */ + const_reverse_iterator rend() const noexcept + { + return crend(); + } + + /*! + @brief returns a const reverse iterator to the last element + + Returns a const iterator to the reverse-beginning; that is, the last + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rbegin()`. + + @liveexample{The following code shows an example for `crbegin()`.,crbegin} + + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(cend()); + } + + /*! + @brief returns a const reverse iterator to one before the first + + Returns a const reverse iterator to the reverse-end; that is, one before + the first element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rend()`. + + @liveexample{The following code shows an example for `crend()`.,crend} + + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(cbegin()); + } + + public: + /*! + @brief wrapper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + For loop without iterator_wrapper: + + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without iterator proxy: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with iterator proxy: + + @code{cpp} + for (auto it : json::iterator_wrapper(j_object)) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). + + @param[in] ref reference to a JSON value + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + + @liveexample{The following code shows how the wrapper is used,iterator_wrapper} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @note The name of this function is not yet final and may change in the + future. + + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use @ref items() instead; + that is, replace `json::iterator_wrapper(j)` with `j.items()`. + */ + JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) + static iteration_proxy iterator_wrapper(reference ref) noexcept + { + return ref.items(); + } + + /*! + @copydoc iterator_wrapper(reference) + */ + JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) + static iteration_proxy iterator_wrapper(const_reference ref) noexcept + { + return ref.items(); + } + + /*! + @brief helper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + For loop without `items()` function: + + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without `items()` function: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with `items()` function: + + @code{cpp} + for (auto& el : j_object.items()) + { + std::cout << "key: " << el.key() << ", value:" << el.value() << '\n'; + } + @endcode + + The `items()` function also allows to use + [structured bindings](https://en.cppreference.com/w/cpp/language/structured_binding) + (C++17): + + @code{cpp} + for (auto& [key, val] : j_object.items()) + { + std::cout << "key: " << key << ", value:" << val << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). For primitive types (e.g., numbers), + `key()` returns an empty string. + + @warning Using `items()` on temporary objects is dangerous. Make sure the + object's lifetime exeeds the iteration. See + for more + information. + + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + + @liveexample{The following code shows how the function is used.,items} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 3.1.0, structured bindings support since 3.5.0. + */ + iteration_proxy items() noexcept + { + return iteration_proxy(*this); + } + + /*! + @copydoc items() + */ + iteration_proxy items() const noexcept + { + return iteration_proxy(*this); + } + + /// @} + + + ////////////// + // capacity // + ////////////// + + /// @name capacity + /// @{ + + /*! + @brief checks whether the container is empty. + + Checks if a JSON value has no elements (i.e. whether its @ref size is `0`). + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `true` + boolean | `false` + string | `false` + number | `false` + binary | `false` + object | result of function `object_t::empty()` + array | result of function `array_t::empty()` + + @liveexample{The following code uses `empty()` to check if a JSON + object contains any elements.,empty} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `empty()` functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return whether a string stored as JSON value + is empty - it returns whether the JSON container itself is empty which is + false in the case of a string. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + - Has the semantics of `begin() == end()`. + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + bool empty() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return true; + } + + case value_t::array: + { + // delegate call to array_t::empty() + return m_value.array->empty(); + } + + case value_t::object: + { + // delegate call to object_t::empty() + return m_value.object->empty(); + } + + default: + { + // all other types are nonempty + return false; + } + } + } + + /*! + @brief returns the number of elements + + Returns the number of elements in a JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` + boolean | `1` + string | `1` + number | `1` + binary | `1` + object | result of function object_t::size() + array | result of function array_t::size() + + @liveexample{The following code calls `size()` on the different value + types.,size} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their size() functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return the length of a string stored as JSON + value - it returns the number of elements in the JSON value which is 1 in + the case of a string. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + - Has the semantics of `std::distance(begin(), end())`. + + @sa @ref empty() -- checks whether the container is empty + @sa @ref max_size() -- returns the maximal number of elements + + @since version 1.0.0 + */ + size_type size() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return 0; + } + + case value_t::array: + { + // delegate call to array_t::size() + return m_value.array->size(); + } + + case value_t::object: + { + // delegate call to object_t::size() + return m_value.object->size(); + } + + default: + { + // all other types have size 1 + return 1; + } + } + } + + /*! + @brief returns the maximum possible number of elements + + Returns the maximum number of elements a JSON value is able to hold due to + system or library implementation limitations, i.e. `std::distance(begin(), + end())` for the JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` (same as `size()`) + boolean | `1` (same as `size()`) + string | `1` (same as `size()`) + number | `1` (same as `size()`) + binary | `1` (same as `size()`) + object | result of function `object_t::max_size()` + array | result of function `array_t::max_size()` + + @liveexample{The following code calls `max_size()` on the different value + types. Note the output is implementation specific.,max_size} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `max_size()` functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + - Has the semantics of returning `b.size()` where `b` is the largest + possible JSON value. + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + size_type max_size() const noexcept + { + switch (m_type) + { + case value_t::array: + { + // delegate call to array_t::max_size() + return m_value.array->max_size(); + } + + case value_t::object: + { + // delegate call to object_t::max_size() + return m_value.object->max_size(); + } + + default: + { + // all other types have max_size() == size() + return size(); + } + } + } + + /// @} + + + /////////////// + // modifiers // + /////////////// + + /// @name modifiers + /// @{ + + /*! + @brief clears the contents + + Clears the content of a JSON value and resets it to the default value as + if @ref basic_json(value_t) would have been called with the current value + type from @ref type(): + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + binary | An empty byte vector + object | `{}` + array | `[]` + + @post Has the same effect as calling + @code {.cpp} + *this = basic_json(type()); + @endcode + + @liveexample{The example below shows the effect of `clear()` to different + JSON types.,clear} + + @complexity Linear in the size of the JSON value. + + @iterators All iterators, pointers and references related to this container + are invalidated. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @sa @ref basic_json(value_t) -- constructor that creates an object with the + same value than calling `clear()` + + @since version 1.0.0 + */ + void clear() noexcept + { + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = 0; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = 0; + break; + } + + case value_t::number_float: + { + m_value.number_float = 0.0; + break; + } + + case value_t::boolean: + { + m_value.boolean = false; + break; + } + + case value_t::string: + { + m_value.string->clear(); + break; + } + + case value_t::binary: + { + m_value.binary->clear(); + break; + } + + case value_t::array: + { + m_value.array->clear(); + break; + } + + case value_t::object: + { + m_value.object->clear(); + break; + } + + default: + break; + } + } + + /*! + @brief add an object to an array + + Appends the given element @a val to the end of the JSON value. If the + function is called on a JSON null value, an empty array is created before + appending @a val. + + @param[in] val the value to add to the JSON array + + @throw type_error.308 when called on a type other than JSON array or + null; example: `"cannot use push_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON array. Note how the `null` value was silently + converted to a JSON array.,push_back} + + @since version 1.0.0 + */ + void push_back(basic_json&& val) + { + // push_back only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (move semantics) + m_value.array->push_back(std::move(val)); + // if val is moved from, basic_json move constructor marks it null so we do not call the destructor + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(basic_json&& val) + { + push_back(std::move(val)); + return *this; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + void push_back(const basic_json& val) + { + // push_back only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array + m_value.array->push_back(val); + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(const basic_json& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + Inserts the given element @a val to the JSON object. If the function is + called on a JSON null value, an empty object is created before inserting + @a val. + + @param[in] val the value to add to the JSON object + + @throw type_error.308 when called on a type other than JSON object or + null; example: `"cannot use push_back() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON object. Note how the `null` value was silently + converted to a JSON object.,push_back__object_t__value} + + @since version 1.0.0 + */ + void push_back(const typename object_t::value_type& val) + { + // push_back only works for null objects or objects + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array + m_value.object->insert(val); + } + + /*! + @brief add an object to an object + @copydoc push_back(const typename object_t::value_type&) + */ + reference operator+=(const typename object_t::value_type& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + This function allows to use `push_back` with an initializer list. In case + + 1. the current value is an object, + 2. the initializer list @a init contains only two elements, and + 3. the first element of @a init is a string, + + @a init is converted into an object element and added using + @ref push_back(const typename object_t::value_type&). Otherwise, @a init + is converted to a JSON value and added using @ref push_back(basic_json&&). + + @param[in] init an initializer list + + @complexity Linear in the size of the initializer list @a init. + + @note This function is required to resolve an ambiguous overload error, + because pairs like `{"key", "value"}` can be both interpreted as + `object_t::value_type` or `std::initializer_list`, see + https://github.com/nlohmann/json/issues/235 for more information. + + @liveexample{The example shows how initializer lists are treated as + objects when possible.,push_back__initializer_list} + */ + void push_back(initializer_list_t init) + { + if (is_object() && init.size() == 2 && (*init.begin())->is_string()) + { + basic_json&& key = init.begin()->moved_or_copied(); + push_back(typename object_t::value_type( + std::move(key.get_ref()), (init.begin() + 1)->moved_or_copied())); + } + else + { + push_back(basic_json(init)); + } + } + + /*! + @brief add an object to an object + @copydoc push_back(initializer_list_t) + */ + reference operator+=(initializer_list_t init) + { + push_back(init); + return *this; + } + + /*! + @brief add an object to an array + + Creates a JSON value from the passed parameters @a args to the end of the + JSON value. If the function is called on a JSON null value, an empty array + is created before appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @return reference to the inserted element + + @throw type_error.311 when called on a type other than JSON array or + null; example: `"cannot use emplace_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` can be used to add + elements to a JSON array. Note how the `null` value was silently converted + to a JSON array.,emplace_back} + + @since version 2.0.8, returns reference since 3.7.0 + */ + template + reference emplace_back(Args&& ... args) + { + // emplace_back only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) + { + JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (perfect forwarding) +#ifdef JSON_HAS_CPP_17 + return m_value.array->emplace_back(std::forward(args)...); +#else + m_value.array->emplace_back(std::forward(args)...); + return m_value.array->back(); +#endif + } + + /*! + @brief add an object to an object if key does not exist + + Inserts a new element into a JSON object constructed in-place with the + given @a args if there is no element with the key in the container. If the + function is called on a JSON null value, an empty object is created before + appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @return a pair consisting of an iterator to the inserted element, or the + already-existing element if no insertion happened, and a bool + denoting whether the insertion took place. + + @throw type_error.311 when called on a type other than JSON object or + null; example: `"cannot use emplace() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `emplace()` can be used to add elements + to a JSON object. Note how the `null` value was silently converted to a + JSON object. Further note how no value is added if there was already one + value stored with the same key.,emplace} + + @since version 2.0.8 + */ + template + std::pair emplace(Args&& ... args) + { + // emplace only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) + { + JSON_THROW(type_error::create(311, "cannot use emplace() with " + std::string(type_name()))); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array (perfect forwarding) + auto res = m_value.object->emplace(std::forward(args)...); + // create result iterator and set iterator to the result of emplace + auto it = begin(); + it.m_it.object_iterator = res.first; + + // return pair of iterator and boolean + return {it, res.second}; + } + + /// Helper for insertion of an iterator + /// @note: This uses std::distance to support GCC 4.8, + /// see https://github.com/nlohmann/json/pull/1257 + template + iterator insert_iterator(const_iterator pos, Args&& ... args) + { + iterator result(this); + JSON_ASSERT(m_value.array != nullptr); + + auto insert_pos = std::distance(m_value.array->begin(), pos.m_it.array_iterator); + m_value.array->insert(pos.m_it.array_iterator, std::forward(args)...); + result.m_it.array_iterator = m_value.array->begin() + insert_pos; + + // This could have been written as: + // result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + // but the return value of insert is missing in GCC 4.8, so it is written this way instead. + + return result; + } + + /*! + @brief inserts element + + Inserts element @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] val element to insert + @return iterator pointing to the inserted @a val. + + @throw type_error.309 if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @complexity Constant plus linear in the distance between @a pos and end of + the container. + + @liveexample{The example shows how `insert()` is used.,insert} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const basic_json& val) + { + // insert only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + return insert_iterator(pos, val); + } + + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + /*! + @brief inserts element + @copydoc insert(const_iterator, const basic_json&) + */ + iterator insert(const_iterator pos, basic_json&& val) + { + return insert(pos, val); + } + + /*! + @brief inserts elements + + Inserts @a cnt copies of @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] cnt number of copies of @a val to insert + @param[in] val element to insert + @return iterator pointing to the first element inserted, or @a pos if + `cnt==0` + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @complexity Linear in @a cnt plus linear in the distance between @a pos + and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__count} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + { + // insert only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + return insert_iterator(pos, cnt, val); + } + + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)` before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + @throw invalid_iterator.211 if @a first or @a last are iterators into + container for which insert is called; example: `"passed iterators may not + belong to container"` + + @return iterator pointing to the first element inserted, or @a pos if + `first==last` + + @complexity Linear in `std::distance(first, last)` plus linear in the + distance between @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__range} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const_iterator first, const_iterator last) + { + // insert only works for arrays + if (JSON_HEDLEY_UNLIKELY(!is_array())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // check if range iterators belong to the same JSON object + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + if (JSON_HEDLEY_UNLIKELY(first.m_object == this)) + { + JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container")); + } + + // insert to array and return iterator + return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator); + } + + /*! + @brief inserts elements + + Inserts elements from initializer list @a ilist before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] ilist initializer list to insert the values from + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @return iterator pointing to the first element inserted, or @a pos if + `ilist` is empty + + @complexity Linear in `ilist.size()` plus linear in the distance between + @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__ilist} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, initializer_list_t ilist) + { + // insert only works for arrays + if (JSON_HEDLEY_UNLIKELY(!is_array())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + return insert_iterator(pos, ilist.begin(), ilist.end()); + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)`. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.309 if called on JSON values other than objects; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity Logarithmic: `O(N*log(size() + N))`, where `N` is the number + of elements to insert. + + @liveexample{The example shows how `insert()` is used.,insert__range_object} + + @since version 3.0.0 + */ + void insert(const_iterator first, const_iterator last) + { + // insert only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if range iterators belong to the same JSON object + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + // passed iterators must belong to objects + if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); + } + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from JSON object @a j and overwrites existing keys. + + @param[in] j JSON object to read values from + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_reference j) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); + } + if (JSON_HEDLEY_UNLIKELY(!j.is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()))); + } + + for (auto it = j.cbegin(); it != j.cend(); ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from from range `[first, last)` and overwrites existing + keys. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used__range.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_iterator first, const_iterator last) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); + } + + // check if range iterators belong to the same JSON object + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + // passed iterators must belong to objects + if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object() + || !last.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + for (auto it = first; it != last; ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of the JSON value with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other JSON value to exchange the contents with + + @complexity Constant. + + @liveexample{The example below shows how JSON values can be swapped with + `swap()`.,swap__reference} + + @since version 1.0.0 + */ + void swap(reference other) noexcept ( + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value&& + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value + ) + { + std::swap(m_type, other.m_type); + std::swap(m_value, other.m_value); + assert_invariant(); + } + + /*! + @brief exchanges the values + + Exchanges the contents of the JSON value from @a left with those of @a right. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. implemented as a friend function callable via ADL. + + @param[in,out] left JSON value to exchange the contents with + @param[in,out] right JSON value to exchange the contents with + + @complexity Constant. + + @liveexample{The example below shows how JSON values can be swapped with + `swap()`.,swap__reference} + + @since version 1.0.0 + */ + friend void swap(reference left, reference right) noexcept ( + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value&& + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value + ) + { + left.swap(right); + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON array with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other array to exchange the contents with + + @throw type_error.310 when JSON value is not an array; example: `"cannot + use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how arrays can be swapped with + `swap()`.,swap__array_t} + + @since version 1.0.0 + */ + void swap(array_t& other) + { + // swap only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + std::swap(*(m_value.array), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON object with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other object to exchange the contents with + + @throw type_error.310 when JSON value is not an object; example: + `"cannot use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how objects can be swapped with + `swap()`.,swap__object_t} + + @since version 1.0.0 + */ + void swap(object_t& other) + { + // swap only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + std::swap(*(m_value.object), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON string with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other string to exchange the contents with + + @throw type_error.310 when JSON value is not a string; example: `"cannot + use swap() with boolean"` + + @complexity Constant. + + @liveexample{The example below shows how strings can be swapped with + `swap()`.,swap__string_t} + + @since version 1.0.0 + */ + void swap(string_t& other) + { + // swap only works for strings + if (JSON_HEDLEY_LIKELY(is_string())) + { + std::swap(*(m_value.string), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON string with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other binary to exchange the contents with + + @throw type_error.310 when JSON value is not a string; example: `"cannot + use swap() with boolean"` + + @complexity Constant. + + @liveexample{The example below shows how strings can be swapped with + `swap()`.,swap__binary_t} + + @since version 3.8.0 + */ + void swap(binary_t& other) + { + // swap only works for strings + if (JSON_HEDLEY_LIKELY(is_binary())) + { + std::swap(*(m_value.binary), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /// @copydoc swap(binary_t) + void swap(typename binary_t::container_type& other) + { + // swap only works for strings + if (JSON_HEDLEY_LIKELY(is_binary())) + { + std::swap(*(m_value.binary), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /// @} + + public: + ////////////////////////////////////////// + // lexicographical comparison operators // + ////////////////////////////////////////// + + /// @name lexicographical comparison operators + /// @{ + + /*! + @brief comparison: equal + + Compares two JSON values for equality according to the following rules: + - Two JSON values are equal if (1) they are from the same type and (2) + their stored values are the same according to their respective + `operator==`. + - Integer and floating-point numbers are automatically converted before + comparison. Note that two NaN values are always treated as unequal. + - Two JSON null values are equal. + + @note Floating-point inside JSON values numbers are compared with + `json::number_float_t::operator==` which is `double::operator==` by + default. To compare floating-point while respecting an epsilon, an alternative + [comparison function](https://github.com/mariokonrad/marnav/blob/master/include/marnav/math/floatingpoint.hpp#L34-#L39) + could be used, for instance + @code {.cpp} + template::value, T>::type> + inline bool is_same(T a, T b, T epsilon = std::numeric_limits::epsilon()) noexcept + { + return std::abs(a - b) <= epsilon; + } + @endcode + Or you can self-defined operator equal function like this: + @code {.cpp} + bool my_equal(const_reference lhs, const_reference rhs) { + const auto lhs_type lhs.type(); + const auto rhs_type rhs.type(); + if (lhs_type == rhs_type) { + switch(lhs_type) + // self_defined case + case value_t::number_float: + return std::abs(lhs - rhs) <= std::numeric_limits::epsilon(); + // other cases remain the same with the original + ... + } + ... + } + @endcode + + @note NaN values never compare equal to themselves or to other NaN values. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are equal + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__equal} + + @since version 1.0.0 + */ + friend bool operator==(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + return *lhs.m_value.array == *rhs.m_value.array; + + case value_t::object: + return *lhs.m_value.object == *rhs.m_value.object; + + case value_t::null: + return true; + + case value_t::string: + return *lhs.m_value.string == *rhs.m_value.string; + + case value_t::boolean: + return lhs.m_value.boolean == rhs.m_value.boolean; + + case value_t::number_integer: + return lhs.m_value.number_integer == rhs.m_value.number_integer; + + case value_t::number_unsigned: + return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; + + case value_t::number_float: + return lhs.m_value.number_float == rhs.m_value.number_float; + + case value_t::binary: + return *lhs.m_value.binary == *rhs.m_value.binary; + + default: + return false; + } + } + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; + } + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); + } + + return false; + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept + { + return lhs == basic_json(rhs); + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) == rhs; + } + + /*! + @brief comparison: not equal + + Compares two JSON values for inequality by calculating `not (lhs == rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are not equal + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__notequal} + + @since version 1.0.0 + */ + friend bool operator!=(const_reference lhs, const_reference rhs) noexcept + { + return !(lhs == rhs); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept + { + return lhs != basic_json(rhs); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) != rhs; + } + + /*! + @brief comparison: less than + + Compares whether one JSON value @a lhs is less than another JSON value @a + rhs according to the following rules: + - If @a lhs and @a rhs have the same type, the values are compared using + the default `<` operator. + - Integer and floating-point numbers are automatically converted before + comparison + - In case @a lhs and @a rhs have different types, the values are ignored + and the order of the types is considered, see + @ref operator<(const value_t, const value_t). + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__less} + + @since version 1.0.0 + */ + friend bool operator<(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + // note parentheses are necessary, see + // https://github.com/nlohmann/json/issues/1530 + return (*lhs.m_value.array) < (*rhs.m_value.array); + + case value_t::object: + return (*lhs.m_value.object) < (*rhs.m_value.object); + + case value_t::null: + return false; + + case value_t::string: + return (*lhs.m_value.string) < (*rhs.m_value.string); + + case value_t::boolean: + return (lhs.m_value.boolean) < (rhs.m_value.boolean); + + case value_t::number_integer: + return (lhs.m_value.number_integer) < (rhs.m_value.number_integer); + + case value_t::number_unsigned: + return (lhs.m_value.number_unsigned) < (rhs.m_value.number_unsigned); + + case value_t::number_float: + return (lhs.m_value.number_float) < (rhs.m_value.number_float); + + case value_t::binary: + return (*lhs.m_value.binary) < (*rhs.m_value.binary); + + default: + return false; + } + } + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; + } + + // We only reach this line if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + return operator<(lhs_type, rhs_type); + } + + /*! + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept + { + return lhs < basic_json(rhs); + } + + /*! + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) < rhs; + } + + /*! + @brief comparison: less than or equal + + Compares whether one JSON value @a lhs is less than or equal to another + JSON value by calculating `not (rhs < lhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than or equal to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greater} + + @since version 1.0.0 + */ + friend bool operator<=(const_reference lhs, const_reference rhs) noexcept + { + return !(rhs < lhs); + } + + /*! + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept + { + return lhs <= basic_json(rhs); + } + + /*! + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) <= rhs; + } + + /*! + @brief comparison: greater than + + Compares whether one JSON value @a lhs is greater than another + JSON value by calculating `not (lhs <= rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__lessequal} + + @since version 1.0.0 + */ + friend bool operator>(const_reference lhs, const_reference rhs) noexcept + { + return !(lhs <= rhs); + } + + /*! + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept + { + return lhs > basic_json(rhs); + } + + /*! + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) > rhs; + } + + /*! + @brief comparison: greater than or equal + + Compares whether one JSON value @a lhs is greater than or equal to another + JSON value by calculating `not (lhs < rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than or equal to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greaterequal} + + @since version 1.0.0 + */ + friend bool operator>=(const_reference lhs, const_reference rhs) noexcept + { + return !(lhs < rhs); + } + + /*! + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept + { + return lhs >= basic_json(rhs); + } + + /*! + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) >= rhs; + } + + /// @} + + /////////////////// + // serialization // + /////////////////// + + /// @name serialization + /// @{ + + /*! + @brief serialize to stream + + Serialize the given JSON value @a j to the output stream @a o. The JSON + value will be serialized using the @ref dump member function. + + - The indentation of the output can be controlled with the member variable + `width` of the output stream @a o. For instance, using the manipulator + `std::setw(4)` on @a o sets the indentation level to `4` and the + serialization result is the same as calling `dump(4)`. + + - The indentation character can be controlled with the member variable + `fill` of the output stream @a o. For instance, the manipulator + `std::setfill('\\t')` sets indentation to use a tab character rather than + the default space character. + + @param[in,out] o stream to serialize to + @param[in] j JSON value to serialize + + @return the stream @a o + + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded + + @complexity Linear. + + @liveexample{The example below shows the serialization with different + parameters to `width` to adjust the indentation level.,operator_serialize} + + @since version 1.0.0; indentation character added in version 3.0.0 + */ + friend std::ostream& operator<<(std::ostream& o, const basic_json& j) + { + // read width member and use it as indentation parameter if nonzero + const bool pretty_print = o.width() > 0; + const auto indentation = pretty_print ? o.width() : 0; + + // reset width to 0 for subsequent calls to this stream + o.width(0); + + // do the actual serialization + serializer s(detail::output_adapter(o), o.fill()); + s.dump(j, pretty_print, false, static_cast(indentation)); + return o; + } + + /*! + @brief serialize to stream + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use + @ref operator<<(std::ostream&, const basic_json&) + instead; that is, replace calls like `j >> o;` with `o << j;`. + @since version 1.0.0; deprecated since version 3.0.0 + */ + JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator<<(std::ostream&, const basic_json&)) + friend std::ostream& operator>>(const basic_json& j, std::ostream& o) + { + return o << j; + } + + /// @} + + + ///////////////////// + // deserialization // + ///////////////////// + + /// @name deserialization + /// @{ + + /*! + @brief deserialize from a compatible input + + @tparam InputType A compatible input, for instance + - an std::istream object + - a FILE pointer + - a C-style array of characters + - a pointer to a null-terminated string of single byte characters + - an object obj for which begin(obj) and end(obj) produces a valid pair of + iterators. + + @param[in] i input to read from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.101 if a parse error occurs; example: `""unexpected end + of input; expected string literal""` + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb or reading from the input @a i has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an array.,parse__array__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__string__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__istream__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function reading + from a contiguous container.,parse__contiguouscontainer__parser_callback_t} + + @since version 2.0.3 (contiguous containers); version 3.9.0 allowed to + ignore comments. + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json parse(InputType&& i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false) + { + basic_json result; + parser(detail::input_adapter(std::forward(i)), cb, allow_exceptions, ignore_comments).parse(true, result); + return result; + } + + /*! + @brief deserialize from a pair of character iterators + + The value_type of the iterator must be a integral type with size of 1, 2 or + 4 bytes, which will be interpreted respectively as UTF-8, UTF-16 and UTF-32. + + @param[in] first iterator to start of character range + @param[in] last iterator to end of character range + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.101 if a parse error occurs; example: `""unexpected end + of input; expected string literal""` + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json parse(IteratorType first, + IteratorType last, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false) + { + basic_json result; + parser(detail::input_adapter(std::move(first), std::move(last)), cb, allow_exceptions, ignore_comments).parse(true, result); + return result; + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) + static basic_json parse(detail::span_input_adapter&& i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false) + { + basic_json result; + parser(i.get(), cb, allow_exceptions, ignore_comments).parse(true, result); + return result; + } + + /*! + @brief check if the input is valid JSON + + Unlike the @ref parse(InputType&&, const parser_callback_t,const bool) + function, this function neither throws an exception in case of invalid JSON + input (i.e., a parse error) nor creates diagnostic information. + + @tparam InputType A compatible input, for instance + - an std::istream object + - a FILE pointer + - a C-style array of characters + - a pointer to a null-terminated string of single byte characters + - an object obj for which begin(obj) and end(obj) produces a valid pair of + iterators. + + @param[in] i input to read from + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default) + + @return Whether the input read from @a i is valid JSON. + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `accept()` function reading + from a string.,accept__string} + */ + template + static bool accept(InputType&& i, + const bool ignore_comments = false) + { + return parser(detail::input_adapter(std::forward(i)), nullptr, false, ignore_comments).accept(true); + } + + template + static bool accept(IteratorType first, IteratorType last, + const bool ignore_comments = false) + { + return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments).accept(true); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len)) + static bool accept(detail::span_input_adapter&& i, + const bool ignore_comments = false) + { + return parser(i.get(), nullptr, false, ignore_comments).accept(true); + } + + /*! + @brief generate SAX events + + The SAX event lister must follow the interface of @ref json_sax. + + This function reads from a compatible input. Examples are: + - an std::istream object + - a FILE pointer + - a C-style array of characters + - a pointer to a null-terminated string of single byte characters + - an object obj for which begin(obj) and end(obj) produces a valid pair of + iterators. + + @param[in] i input to read from + @param[in,out] sax SAX event listener + @param[in] format the format to parse (JSON, CBOR, MessagePack, or UBJSON) + @param[in] strict whether the input has to be consumed completely + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default); only applies to the JSON file format. + + @return return value of the last processed SAX event + + @throw parse_error.101 if a parse error occurs; example: `""unexpected end + of input; expected string literal""` + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the SAX consumer @a sax has + a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `sax_parse()` function + reading from string and processing the events with a user-defined SAX + event consumer.,sax_parse} + + @since version 3.2.0 + */ + template + JSON_HEDLEY_NON_NULL(2) + static bool sax_parse(InputType&& i, SAX* sax, + input_format_t format = input_format_t::json, + const bool strict = true, + const bool ignore_comments = false) + { + auto ia = detail::input_adapter(std::forward(i)); + return format == input_format_t::json + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) + : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); + } + + template + JSON_HEDLEY_NON_NULL(3) + static bool sax_parse(IteratorType first, IteratorType last, SAX* sax, + input_format_t format = input_format_t::json, + const bool strict = true, + const bool ignore_comments = false) + { + auto ia = detail::input_adapter(std::move(first), std::move(last)); + return format == input_format_t::json + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) + : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); + } + + template + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, sax_parse(ptr, ptr + len, ...)) + JSON_HEDLEY_NON_NULL(2) + static bool sax_parse(detail::span_input_adapter&& i, SAX* sax, + input_format_t format = input_format_t::json, + const bool strict = true, + const bool ignore_comments = false) + { + auto ia = i.get(); + return format == input_format_t::json + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) + : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); + } + + /*! + @brief deserialize from stream + @deprecated This stream operator is deprecated and will be removed in + version 4.0.0 of the library. Please use + @ref operator>>(std::istream&, basic_json&) + instead; that is, replace calls like `j << i;` with `i >> j;`. + @since version 1.0.0; deprecated since version 3.0.0 + */ + JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator>>(std::istream&, basic_json&)) + friend std::istream& operator<<(basic_json& j, std::istream& i) + { + return operator>>(i, j); + } + + /*! + @brief deserialize from stream + + Deserializes an input stream to a JSON value. + + @param[in,out] i input stream to read a serialized JSON value from + @param[in,out] j JSON value to write the deserialized input to + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below shows how a JSON value is constructed by + reading a serialization from a stream.,operator_deserialize} + + @sa parse(std::istream&, const parser_callback_t) for a variant with a + parser callback function to filter values while parsing + + @since version 1.0.0 + */ + friend std::istream& operator>>(std::istream& i, basic_json& j) + { + parser(detail::input_adapter(i)).parse(false, j); + return i; + } + + /// @} + + /////////////////////////// + // convenience functions // + /////////////////////////// + + /*! + @brief return the type as string + + Returns the type name as string to be used in error messages - usually to + indicate that a function was called on a wrong JSON type. + + @return a string representation of a the @a m_type member: + Value type | return value + ----------- | ------------- + null | `"null"` + boolean | `"boolean"` + string | `"string"` + number | `"number"` (for all number types) + object | `"object"` + array | `"array"` + binary | `"binary"` + discarded | `"discarded"` + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @complexity Constant. + + @liveexample{The following code exemplifies `type_name()` for all JSON + types.,type_name} + + @sa @ref type() -- return the type of the JSON value + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + + @since version 1.0.0, public since 2.1.0, `const char*` and `noexcept` + since 3.0.0 + */ + JSON_HEDLEY_RETURNS_NON_NULL + const char* type_name() const noexcept + { + { + switch (m_type) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::binary: + return "binary"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } + } + } + + + private: + ////////////////////// + // member variables // + ////////////////////// + + /// the type of the current element + value_t m_type = value_t::null; + + /// the value of the current element + json_value m_value = {}; + + ////////////////////////////////////////// + // binary serialization/deserialization // + ////////////////////////////////////////// + + /// @name binary serialization/deserialization support + /// @{ + + public: + /*! + @brief create a CBOR serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the CBOR (Concise + Binary Object Representation) serialization format. CBOR is a binary + serialization format which aims to be more compact than JSON itself, yet + more efficient to parse. + + The library uses the following mapping from JSON values types to + CBOR types according to the CBOR specification (RFC 7049): + + JSON value type | value/range | CBOR type | first byte + --------------- | ------------------------------------------ | ---------------------------------- | --------------- + null | `null` | Null | 0xF6 + boolean | `true` | True | 0xF5 + boolean | `false` | False | 0xF4 + number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B + number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A + number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 + number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 + number_integer | -24..-1 | Negative integer | 0x20..0x37 + number_integer | 0..23 | Integer | 0x00..0x17 + number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 + number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 + number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B + number_unsigned | 0..23 | Integer | 0x00..0x17 + number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 + number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 + number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B + number_float | *any value representable by a float* | Single-Precision Float | 0xFA + number_float | *any value NOT representable by a float* | Double-Precision Float | 0xFB + string | *length*: 0..23 | UTF-8 string | 0x60..0x77 + string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78 + string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79 + string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A + string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B + array | *size*: 0..23 | array | 0x80..0x97 + array | *size*: 23..255 | array (1 byte follow) | 0x98 + array | *size*: 256..65535 | array (2 bytes follow) | 0x99 + array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9A + array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B + object | *size*: 0..23 | map | 0xA0..0xB7 + object | *size*: 23..255 | map (1 byte follow) | 0xB8 + object | *size*: 256..65535 | map (2 bytes follow) | 0xB9 + object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA + object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB + binary | *size*: 0..23 | byte string | 0x40..0x57 + binary | *size*: 23..255 | byte string (1 byte follow) | 0x58 + binary | *size*: 256..65535 | byte string (2 bytes follow) | 0x59 + binary | *size*: 65536..4294967295 | byte string (4 bytes follow) | 0x5A + binary | *size*: 4294967296..18446744073709551615 | byte string (8 bytes follow) | 0x5B + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a CBOR value. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The following CBOR types are not used in the conversion: + - UTF-8 strings terminated by "break" (0x7F) + - arrays terminated by "break" (0x9F) + - maps terminated by "break" (0xBF) + - byte strings terminated by "break" (0x5F) + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) + - half-precision floats (0xF9) + - break (0xFF) + + @param[in] j JSON value to serialize + @return CBOR serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in CBOR format.,to_cbor} + + @sa http://cbor.io + @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the + analogous deserialization + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format + + @since version 2.0.9; compact representation of floating-point numbers + since version 3.8.0 + */ + static std::vector to_cbor(const basic_json& j) + { + std::vector result; + to_cbor(j, result); + return result; + } + + static void to_cbor(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_cbor(j); + } + + static void to_cbor(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_cbor(j); + } + + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the MessagePack + serialization format. MessagePack is a binary serialization format which + aims to be more compact than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + MessagePack types according to the MessagePack specification: + + JSON value type | value/range | MessagePack type | first byte + --------------- | --------------------------------- | ---------------- | ---------- + null | `null` | nil | 0xC0 + boolean | `true` | true | 0xC3 + boolean | `false` | false | 0xC2 + number_integer | -9223372036854775808..-2147483649 | int64 | 0xD3 + number_integer | -2147483648..-32769 | int32 | 0xD2 + number_integer | -32768..-129 | int16 | 0xD1 + number_integer | -128..-33 | int8 | 0xD0 + number_integer | -32..-1 | negative fixint | 0xE0..0xFF + number_integer | 0..127 | positive fixint | 0x00..0x7F + number_integer | 128..255 | uint 8 | 0xCC + number_integer | 256..65535 | uint 16 | 0xCD + number_integer | 65536..4294967295 | uint 32 | 0xCE + number_integer | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_unsigned | 0..127 | positive fixint | 0x00..0x7F + number_unsigned | 128..255 | uint 8 | 0xCC + number_unsigned | 256..65535 | uint 16 | 0xCD + number_unsigned | 65536..4294967295 | uint 32 | 0xCE + number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_float | *any value representable by a float* | float 32 | 0xCA + number_float | *any value NOT representable by a float* | float 64 | 0xCB + string | *length*: 0..31 | fixstr | 0xA0..0xBF + string | *length*: 32..255 | str 8 | 0xD9 + string | *length*: 256..65535 | str 16 | 0xDA + string | *length*: 65536..4294967295 | str 32 | 0xDB + array | *size*: 0..15 | fixarray | 0x90..0x9F + array | *size*: 16..65535 | array 16 | 0xDC + array | *size*: 65536..4294967295 | array 32 | 0xDD + object | *size*: 0..15 | fix map | 0x80..0x8F + object | *size*: 16..65535 | map 16 | 0xDE + object | *size*: 65536..4294967295 | map 32 | 0xDF + binary | *size*: 0..255 | bin 8 | 0xC4 + binary | *size*: 256..65535 | bin 16 | 0xC5 + binary | *size*: 65536..4294967295 | bin 32 | 0xC6 + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a MessagePack value. + + @note The following values can **not** be converted to a MessagePack value: + - strings with more than 4294967295 bytes + - byte strings with more than 4294967295 bytes + - arrays with more than 4294967295 elements + - objects with more than 4294967295 elements + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in MessagePack format.,to_msgpack} + + @sa http://msgpack.org + @sa @ref from_msgpack for the analogous deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format + + @since version 2.0.9 + */ + static std::vector to_msgpack(const basic_json& j) + { + std::vector result; + to_msgpack(j, result); + return result; + } + + static void to_msgpack(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_msgpack(j); + } + + static void to_msgpack(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_msgpack(j); + } + + /*! + @brief create a UBJSON serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the UBJSON + (Universal Binary JSON) serialization format. UBJSON aims to be more compact + than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + UBJSON types according to the UBJSON specification: + + JSON value type | value/range | UBJSON type | marker + --------------- | --------------------------------- | ----------- | ------ + null | `null` | null | `Z` + boolean | `true` | true | `T` + boolean | `false` | false | `F` + number_integer | -9223372036854775808..-2147483649 | int64 | `L` + number_integer | -2147483648..-32769 | int32 | `l` + number_integer | -32768..-129 | int16 | `I` + number_integer | -128..127 | int8 | `i` + number_integer | 128..255 | uint8 | `U` + number_integer | 256..32767 | int16 | `I` + number_integer | 32768..2147483647 | int32 | `l` + number_integer | 2147483648..9223372036854775807 | int64 | `L` + number_unsigned | 0..127 | int8 | `i` + number_unsigned | 128..255 | uint8 | `U` + number_unsigned | 256..32767 | int16 | `I` + number_unsigned | 32768..2147483647 | int32 | `l` + number_unsigned | 2147483648..9223372036854775807 | int64 | `L` + number_unsigned | 2147483649..18446744073709551615 | high-precision | `H` + number_float | *any value* | float64 | `D` + string | *with shortest length indicator* | string | `S` + array | *see notes on optimized format* | array | `[` + object | *see notes on optimized format* | map | `{` + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a UBJSON value. + + @note The following values can **not** be converted to a UBJSON value: + - strings with more than 9223372036854775807 bytes (theoretical) + + @note The following markers are not used in the conversion: + - `Z`: no-op values are not created. + - `C`: single-byte strings are serialized with `S` markers. + + @note Any UBJSON output created @ref to_ubjson can be successfully parsed + by @ref from_ubjson. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The optimized formats for containers are supported: Parameter + @a use_size adds size information to the beginning of a container and + removes the closing marker. Parameter @a use_type further checks + whether all elements of a container have the same type and adds the + type marker to the beginning of the container. The @a use_type + parameter must only be used together with @a use_size = true. Note + that @a use_size = true alone may result in larger representations - + the benefit of this parameter is that the receiving side is + immediately informed on the number of elements of the container. + + @note If the JSON data contains the binary type, the value stored is a list + of integers, as suggested by the UBJSON documentation. In particular, + this means that serialization and the deserialization of a JSON + containing binary values into UBJSON and back will result in a + different JSON object. + + @param[in] j JSON value to serialize + @param[in] use_size whether to add size annotations to container types + @param[in] use_type whether to add type annotations to container types + (must be combined with @a use_size = true) + @return UBJSON serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in UBJSON format.,to_ubjson} + + @sa http://ubjson.org + @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the + analogous deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + + @since version 3.1.0 + */ + static std::vector to_ubjson(const basic_json& j, + const bool use_size = false, + const bool use_type = false) + { + std::vector result; + to_ubjson(j, result, use_size, use_type); + return result; + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + + /*! + @brief Serializes the given JSON object `j` to BSON and returns a vector + containing the corresponding BSON-representation. + + BSON (Binary JSON) is a binary format in which zero or more ordered key/value pairs are + stored as a single entity (a so-called document). + + The library uses the following mapping from JSON values types to BSON types: + + JSON value type | value/range | BSON type | marker + --------------- | --------------------------------- | ----------- | ------ + null | `null` | null | 0x0A + boolean | `true`, `false` | boolean | 0x08 + number_integer | -9223372036854775808..-2147483649 | int64 | 0x12 + number_integer | -2147483648..2147483647 | int32 | 0x10 + number_integer | 2147483648..9223372036854775807 | int64 | 0x12 + number_unsigned | 0..2147483647 | int32 | 0x10 + number_unsigned | 2147483648..9223372036854775807 | int64 | 0x12 + number_unsigned | 9223372036854775808..18446744073709551615| -- | -- + number_float | *any value* | double | 0x01 + string | *any value* | string | 0x02 + array | *any value* | document | 0x04 + object | *any value* | document | 0x03 + binary | *any value* | binary | 0x05 + + @warning The mapping is **incomplete**, since only JSON-objects (and things + contained therein) can be serialized to BSON. + Also, integers larger than 9223372036854775807 cannot be serialized to BSON, + and the keys may not contain U+0000, since they are serialized a + zero-terminated c-strings. + + @throw out_of_range.407 if `j.is_number_unsigned() && j.get() > 9223372036854775807` + @throw out_of_range.409 if a key in `j` contains a NULL (U+0000) + @throw type_error.317 if `!j.is_object()` + + @pre The input `j` is required to be an object: `j.is_object() == true`. + + @note Any BSON output created via @ref to_bson can be successfully parsed + by @ref from_bson. + + @param[in] j JSON value to serialize + @return BSON serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in BSON format.,to_bson} + + @sa http://bsonspec.org/spec.html + @sa @ref from_bson(detail::input_adapter&&, const bool strict) for the + analogous deserialization + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format + @sa @ref to_cbor(const basic_json&) for the related CBOR format + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + */ + static std::vector to_bson(const basic_json& j) + { + std::vector result; + to_bson(j, result); + return result; + } + + /*! + @brief Serializes the given JSON object `j` to BSON and forwards the + corresponding BSON-representation to the given output_adapter `o`. + @param j The JSON object to convert to BSON. + @param o The output adapter that receives the binary BSON representation. + @pre The input `j` shall be an object: `j.is_object() == true` + @sa @ref to_bson(const basic_json&) + */ + static void to_bson(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_bson(j); + } + + /*! + @copydoc to_bson(const basic_json&, detail::output_adapter) + */ + static void to_bson(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_bson(j); + } + + + /*! + @brief create a JSON value from an input in CBOR format + + Deserializes a given input @a i to a JSON value using the CBOR (Concise + Binary Object Representation) serialization format. + + The library maps CBOR types to JSON value types as follows: + + CBOR type | JSON value type | first byte + ---------------------- | --------------- | ---------- + Integer | number_unsigned | 0x00..0x17 + Unsigned integer | number_unsigned | 0x18 + Unsigned integer | number_unsigned | 0x19 + Unsigned integer | number_unsigned | 0x1A + Unsigned integer | number_unsigned | 0x1B + Negative integer | number_integer | 0x20..0x37 + Negative integer | number_integer | 0x38 + Negative integer | number_integer | 0x39 + Negative integer | number_integer | 0x3A + Negative integer | number_integer | 0x3B + Byte string | binary | 0x40..0x57 + Byte string | binary | 0x58 + Byte string | binary | 0x59 + Byte string | binary | 0x5A + Byte string | binary | 0x5B + UTF-8 string | string | 0x60..0x77 + UTF-8 string | string | 0x78 + UTF-8 string | string | 0x79 + UTF-8 string | string | 0x7A + UTF-8 string | string | 0x7B + UTF-8 string | string | 0x7F + array | array | 0x80..0x97 + array | array | 0x98 + array | array | 0x99 + array | array | 0x9A + array | array | 0x9B + array | array | 0x9F + map | object | 0xA0..0xB7 + map | object | 0xB8 + map | object | 0xB9 + map | object | 0xBA + map | object | 0xBB + map | object | 0xBF + False | `false` | 0xF4 + True | `true` | 0xF5 + Null | `null` | 0xF6 + Half-Precision Float | number_float | 0xF9 + Single-Precision Float | number_float | 0xFA + Double-Precision Float | number_float | 0xFB + + @warning The mapping is **incomplete** in the sense that not all CBOR + types can be converted to a JSON value. The following CBOR types + are not supported and will yield parse errors (parse_error.112): + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) + + @warning CBOR allows map keys of any type, whereas JSON only allows + strings as keys in object values. Therefore, CBOR maps with keys + other than UTF-8 strings are rejected (parse_error.113). + + @note Any CBOR output created @ref to_cbor can be successfully parsed by + @ref from_cbor. + + @param[in] i an input in CBOR format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + @param[in] tag_handler how to treat CBOR tags (optional, error by default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if unsupported features from CBOR were + used in the given input @a v or if the input is not valid CBOR + @throw parse_error.113 if a string was expected as map key, but not found + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in CBOR + format to a JSON value.,from_cbor} + + @sa http://cbor.io + @sa @ref to_cbor(const basic_json&) for the analogous serialization + @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for the + related MessagePack format + @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the + related UBJSON format + + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0; added @a allow_exceptions parameter + since 3.2.0; added @a tag_handler parameter since 3.9.0. + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_cbor(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward(i)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + return res ? result : basic_json(value_t::discarded); + } + + /*! + @copydoc from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_cbor(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + return res ? result : basic_json(value_t::discarded); + } + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) + static basic_json from_cbor(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler); + } + + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) + static basic_json from_cbor(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = i.get(); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + return res ? result : basic_json(value_t::discarded); + } + + /*! + @brief create a JSON value from an input in MessagePack format + + Deserializes a given input @a i to a JSON value using the MessagePack + serialization format. + + The library maps MessagePack types to JSON value types as follows: + + MessagePack type | JSON value type | first byte + ---------------- | --------------- | ---------- + positive fixint | number_unsigned | 0x00..0x7F + fixmap | object | 0x80..0x8F + fixarray | array | 0x90..0x9F + fixstr | string | 0xA0..0xBF + nil | `null` | 0xC0 + false | `false` | 0xC2 + true | `true` | 0xC3 + float 32 | number_float | 0xCA + float 64 | number_float | 0xCB + uint 8 | number_unsigned | 0xCC + uint 16 | number_unsigned | 0xCD + uint 32 | number_unsigned | 0xCE + uint 64 | number_unsigned | 0xCF + int 8 | number_integer | 0xD0 + int 16 | number_integer | 0xD1 + int 32 | number_integer | 0xD2 + int 64 | number_integer | 0xD3 + str 8 | string | 0xD9 + str 16 | string | 0xDA + str 32 | string | 0xDB + array 16 | array | 0xDC + array 32 | array | 0xDD + map 16 | object | 0xDE + map 32 | object | 0xDF + bin 8 | binary | 0xC4 + bin 16 | binary | 0xC5 + bin 32 | binary | 0xC6 + ext 8 | binary | 0xC7 + ext 16 | binary | 0xC8 + ext 32 | binary | 0xC9 + fixext 1 | binary | 0xD4 + fixext 2 | binary | 0xD5 + fixext 4 | binary | 0xD6 + fixext 8 | binary | 0xD7 + fixext 16 | binary | 0xD8 + negative fixint | number_integer | 0xE0-0xFF + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + + @param[in] i an input in MessagePack format convertible to an input + adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if unsupported features from MessagePack were + used in the given input @a i or if the input is not valid MessagePack + @throw parse_error.113 if a string was expected as map key, but not found + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + MessagePack format to a JSON value.,from_msgpack} + + @sa http://msgpack.org + @sa @ref to_msgpack(const basic_json&) for the analogous serialization + @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the + related CBOR format + @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for + the related UBJSON format + @sa @ref from_bson(detail::input_adapter&&, const bool, const bool) for + the related BSON format + + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0; added @a allow_exceptions parameter + since 3.2.0 + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_msgpack(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward(i)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /*! + @copydoc from_msgpack(detail::input_adapter&&, const bool, const bool) + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_msgpack(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) + static basic_json from_msgpack(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) + { + return from_msgpack(ptr, ptr + len, strict, allow_exceptions); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) + static basic_json from_msgpack(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = i.get(); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + + /*! + @brief create a JSON value from an input in UBJSON format + + Deserializes a given input @a i to a JSON value using the UBJSON (Universal + Binary JSON) serialization format. + + The library maps UBJSON types to JSON value types as follows: + + UBJSON type | JSON value type | marker + ----------- | --------------------------------------- | ------ + no-op | *no value, next value is read* | `N` + null | `null` | `Z` + false | `false` | `F` + true | `true` | `T` + float32 | number_float | `d` + float64 | number_float | `D` + uint8 | number_unsigned | `U` + int8 | number_integer | `i` + int16 | number_integer | `I` + int32 | number_integer | `l` + int64 | number_integer | `L` + high-precision number | number_integer, number_unsigned, or number_float - depends on number string | 'H' + string | string | `S` + char | string | `C` + array | array (optimized values are supported) | `[` + object | object (optimized values are supported) | `{` + + @note The mapping is **complete** in the sense that any UBJSON value can + be converted to a JSON value. + + @param[in] i an input in UBJSON format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if a parse error occurs + @throw parse_error.113 if a string could not be parsed successfully + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + UBJSON format to a JSON value.,from_ubjson} + + @sa http://ubjson.org + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + analogous serialization + @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the + related CBOR format + @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for + the related MessagePack format + @sa @ref from_bson(detail::input_adapter&&, const bool, const bool) for + the related BSON format + + @since version 3.1.0; added @a allow_exceptions parameter since 3.2.0 + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_ubjson(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward(i)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /*! + @copydoc from_ubjson(detail::input_adapter&&, const bool, const bool) + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_ubjson(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) + static basic_json from_ubjson(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) + { + return from_ubjson(ptr, ptr + len, strict, allow_exceptions); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) + static basic_json from_ubjson(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = i.get(); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + + /*! + @brief Create a JSON value from an input in BSON format + + Deserializes a given input @a i to a JSON value using the BSON (Binary JSON) + serialization format. + + The library maps BSON record types to JSON value types as follows: + + BSON type | BSON marker byte | JSON value type + --------------- | ---------------- | --------------------------- + double | 0x01 | number_float + string | 0x02 | string + document | 0x03 | object + array | 0x04 | array + binary | 0x05 | still unsupported + undefined | 0x06 | still unsupported + ObjectId | 0x07 | still unsupported + boolean | 0x08 | boolean + UTC Date-Time | 0x09 | still unsupported + null | 0x0A | null + Regular Expr. | 0x0B | still unsupported + DB Pointer | 0x0C | still unsupported + JavaScript Code | 0x0D | still unsupported + Symbol | 0x0E | still unsupported + JavaScript Code | 0x0F | still unsupported + int32 | 0x10 | number_integer + Timestamp | 0x11 | still unsupported + 128-bit decimal float | 0x13 | still unsupported + Max Key | 0x7F | still unsupported + Min Key | 0xFF | still unsupported + + @warning The mapping is **incomplete**. The unsupported mappings + are indicated in the table above. + + @param[in] i an input in BSON format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.114 if an unsupported BSON record type is encountered + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + BSON format to a JSON value.,from_bson} + + @sa http://bsonspec.org/spec.html + @sa @ref to_bson(const basic_json&) for the analogous serialization + @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the + related CBOR format + @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for + the related MessagePack format + @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the + related UBJSON format + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bson(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward(i)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /*! + @copydoc from_bson(detail::input_adapter&&, const bool, const bool) + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bson(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) + static basic_json from_bson(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) + { + return from_bson(ptr, ptr + len, strict, allow_exceptions); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) + static basic_json from_bson(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = i.get(); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + /// @} + + ////////////////////////// + // JSON Pointer support // + ////////////////////////// + + /// @name JSON Pointer functions + /// @{ + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. Similar to @ref operator[](const typename + object_t::key_type&), `null` values are created in arrays and objects if + necessary. + + In particular: + - If the JSON pointer points to an object key that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. + - If the JSON pointer points to an array index that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. All indices between the current maximum and the given + index are also filled with `null`. + - The special value `-` is treated as a synonym for the index past the + end. + + @param[in] ptr a JSON pointer + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + + @liveexample{The behavior is shown in the example.,operatorjson_pointer} + + @since version 2.0.0 + */ + reference operator[](const json_pointer& ptr) + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. The function does not change the JSON + value; no `null` values are created. In particular, the special value + `-` yields an exception. + + @param[in] ptr JSON pointer to the desired element + + @return const reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + + @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} + + @since version 2.0.0 + */ + const_reference operator[](const json_pointer& ptr) const + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a reference to the element at with specified JSON pointer @a ptr, + with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. + + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. + + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer} + */ + reference at(const json_pointer& ptr) + { + return ptr.get_checked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a const reference to the element at with specified JSON pointer @a + ptr, with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. + + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. + + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer_const} + */ + const_reference at(const json_pointer& ptr) const + { + return ptr.get_checked(this); + } + + /*! + @brief return flattened JSON value + + The function creates a JSON object whose keys are JSON pointers (see [RFC + 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all + primitive. The original JSON value can be restored using the @ref + unflatten() function. + + @return an object that maps JSON pointers to primitive values + + @note Empty objects and arrays are flattened to `null` and will not be + reconstructed correctly by the @ref unflatten() function. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a JSON object is flattened to an + object whose keys consist of JSON pointers.,flatten} + + @sa @ref unflatten() for the reverse function + + @since version 2.0.0 + */ + basic_json flatten() const + { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } + + /*! + @brief unflatten a previously flattened JSON value + + The function restores the arbitrary nesting of a JSON value that has been + flattened before using the @ref flatten() function. The JSON value must + meet certain constraints: + 1. The value must be an object. + 2. The keys must be JSON pointers (see + [RFC 6901](https://tools.ietf.org/html/rfc6901)) + 3. The mapped values must be primitive JSON types. + + @return the original JSON from a flattened version + + @note Empty objects and arrays are flattened by @ref flatten() to `null` + values and can not unflattened to their original type. Apart from + this example, for a JSON value `j`, the following is always true: + `j == j.flatten().unflatten()`. + + @complexity Linear in the size the JSON value. + + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + + @liveexample{The following code shows how a flattened JSON object is + unflattened into the original nested JSON object.,unflatten} + + @sa @ref flatten() for the reverse function + + @since version 2.0.0 + */ + basic_json unflatten() const + { + return json_pointer::unflatten(*this); + } + + /// @} + + ////////////////////////// + // JSON Patch functions // + ////////////////////////// + + /// @name JSON Patch functions + /// @{ + + /*! + @brief applies a JSON patch + + [JSON Patch](http://jsonpatch.com) defines a JSON document structure for + expressing a sequence of operations to apply to a JSON) document. With + this function, a JSON Patch is applied to the current JSON value by + executing all operations from the patch. + + @param[in] json_patch JSON patch document + @return patched document + + @note The application of a patch is atomic: Either all operations succeed + and the patched document is returned or an exception is thrown. In + any case, the original value is not changed: the patch is applied + to a copy of the value. + + @throw parse_error.104 if the JSON patch does not consist of an array of + objects + + @throw parse_error.105 if the JSON patch is malformed (e.g., mandatory + attributes are missing); example: `"operation add must have member path"` + + @throw out_of_range.401 if an array index is out of range. + + @throw out_of_range.403 if a JSON pointer inside the patch could not be + resolved successfully in the current JSON value; example: `"key baz not + found"` + + @throw out_of_range.405 if JSON pointer has no parent ("add", "remove", + "move") + + @throw other_error.501 if "test" operation was unsuccessful + + @complexity Linear in the size of the JSON value and the length of the + JSON patch. As usually only a fraction of the JSON value is affected by + the patch, the complexity can usually be neglected. + + @liveexample{The following code shows how a JSON patch is applied to a + value.,patch} + + @sa @ref diff -- create a JSON patch by comparing two JSON values + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + basic_json patch(const basic_json& json_patch) const + { + // make a working copy to apply the patch to + basic_json result = *this; + + // the valid JSON Patch operations + enum class patch_operations {add, remove, replace, move, copy, test, invalid}; + + const auto get_op = [](const std::string & op) + { + if (op == "add") + { + return patch_operations::add; + } + if (op == "remove") + { + return patch_operations::remove; + } + if (op == "replace") + { + return patch_operations::replace; + } + if (op == "move") + { + return patch_operations::move; + } + if (op == "copy") + { + return patch_operations::copy; + } + if (op == "test") + { + return patch_operations::test; + } + + return patch_operations::invalid; + }; + + // wrapper for "add" operation; add value at ptr + const auto operation_add = [&result](json_pointer & ptr, basic_json val) + { + // adding to the root of the target document means replacing it + if (ptr.empty()) + { + result = val; + return; + } + + // make sure the top element of the pointer exists + json_pointer top_pointer = ptr.top(); + if (top_pointer != ptr) + { + result.at(top_pointer); + } + + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.back(); + ptr.pop_back(); + basic_json& parent = result[ptr]; + + switch (parent.m_type) + { + case value_t::null: + case value_t::object: + { + // use operator[] to add value + parent[last_path] = val; + break; + } + + case value_t::array: + { + if (last_path == "-") + { + // special case: append to back + parent.push_back(val); + } + else + { + const auto idx = json_pointer::array_index(last_path); + if (JSON_HEDLEY_UNLIKELY(idx > parent.size())) + { + // avoid undefined behavior + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + + // default case: insert add offset + parent.insert(parent.begin() + static_cast(idx), val); + } + break; + } + + // if there exists a parent it cannot be primitive + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + }; + + // wrapper for "remove" operation; remove value at ptr + const auto operation_remove = [&result](json_pointer & ptr) + { + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.back(); + ptr.pop_back(); + basic_json& parent = result.at(ptr); + + // remove child + if (parent.is_object()) + { + // perform range check + auto it = parent.find(last_path); + if (JSON_HEDLEY_LIKELY(it != parent.end())) + { + parent.erase(it); + } + else + { + JSON_THROW(out_of_range::create(403, "key '" + last_path + "' not found")); + } + } + else if (parent.is_array()) + { + // note erase performs range check + parent.erase(json_pointer::array_index(last_path)); + } + }; + + // type check: top level value must be an array + if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + // iterate and apply the operations + for (const auto& val : json_patch) + { + // wrapper to get a value for an operation + const auto get_value = [&val](const std::string & op, + const std::string & member, + bool string_type) -> basic_json & + { + // find value + auto it = val.m_value.object->find(member); + + // context-sensitive error message + const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; + + // check if desired value is present + if (JSON_HEDLEY_UNLIKELY(it == val.m_value.object->end())) + { + JSON_THROW(parse_error::create(105, 0, error_msg + " must have member '" + member + "'")); + } + + // check if result is of type string + if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string())) + { + JSON_THROW(parse_error::create(105, 0, error_msg + " must have string member '" + member + "'")); + } + + // no error: return value + return it->second; + }; + + // type check: every element of the array must be an object + if (JSON_HEDLEY_UNLIKELY(!val.is_object())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + // collect mandatory members + const auto op = get_value("op", "op", true).template get(); + const auto path = get_value(op, "path", true).template get(); + json_pointer ptr(path); + + switch (get_op(op)) + { + case patch_operations::add: + { + operation_add(ptr, get_value("add", "value", false)); + break; + } + + case patch_operations::remove: + { + operation_remove(ptr); + break; + } + + case patch_operations::replace: + { + // the "path" location must exist - use at() + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: + { + const auto from_path = get_value("move", "from", true).template get(); + json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The move operation is functionally identical to a + // "remove" operation on the "from" location, followed + // immediately by an "add" operation at the target + // location with the value that was just removed. + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: + { + const auto from_path = get_value("copy", "from", true).template get(); + const json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The copy is functionally identical to an "add" + // operation at the target location using the value + // specified in the "from" member. + operation_add(ptr, v); + break; + } + + case patch_operations::test: + { + bool success = false; + JSON_TRY + { + // check if "value" matches the one at "path" + // the "path" location must exist - use at() + success = (result.at(ptr) == get_value("test", "value", false)); + } + JSON_INTERNAL_CATCH (out_of_range&) + { + // ignore out of range errors: success remains false + } + + // throw an exception if test fails + if (JSON_HEDLEY_UNLIKELY(!success)) + { + JSON_THROW(other_error::create(501, "unsuccessful: " + val.dump())); + } + + break; + } + + default: + { + // op must be "add", "remove", "replace", "move", "copy", or + // "test" + JSON_THROW(parse_error::create(105, 0, "operation value '" + op + "' is invalid")); + } + } + } + + return result; + } + + /*! + @brief creates a diff as a JSON patch + + Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can + be changed into the value @a target by calling @ref patch function. + + @invariant For two JSON values @a source and @a target, the following code + yields always `true`: + @code {.cpp} + source.patch(diff(source, target)) == target; + @endcode + + @note Currently, only `remove`, `add`, and `replace` operations are + generated. + + @param[in] source JSON value to compare from + @param[in] target JSON value to compare against + @param[in] path helper value to create JSON pointers + + @return a JSON patch to convert the @a source to @a target + + @complexity Linear in the lengths of @a source and @a target. + + @liveexample{The following code shows how a JSON patch is created as a + diff for two JSON values.,diff} + + @sa @ref patch -- apply a JSON patch + @sa @ref merge_patch -- apply a JSON Merge Patch + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + + @since version 2.0.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json diff(const basic_json& source, const basic_json& target, + const std::string& path = "") + { + // the patch + basic_json result(value_t::array); + + // if the values are the same, return empty patch + if (source == target) + { + return result; + } + + if (source.type() != target.type()) + { + // different types: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + return result; + } + + switch (source.type()) + { + case value_t::array: + { + // first pass: traverse common elements + std::size_t i = 0; + while (i < source.size() && i < target.size()) + { + // recursive call to compare array values at index i + auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + // i now reached the end of at least one array + // in a second pass, traverse the remaining elements + + // remove my remaining elements + const auto end_index = static_cast(result.size()); + while (i < source.size()) + { + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( + { + {"op", "remove"}, + {"path", path + "/" + std::to_string(i)} + })); + ++i; + } + + // add other remaining elements + while (i < target.size()) + { + result.push_back( + { + {"op", "add"}, + {"path", path + "/-"}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: + { + // first pass: traverse this object's elements + for (auto it = source.cbegin(); it != source.cend(); ++it) + { + // escape the key name to be used in a JSON patch + const auto key = json_pointer::escape(it.key()); + + if (target.find(it.key()) != target.end()) + { + // recursive call to compare object values at key it + auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } + else + { + // found a key that is not in o -> remove it + result.push_back(object( + { + {"op", "remove"}, {"path", path + "/" + key} + })); + } + } + + // second pass: traverse other object's elements + for (auto it = target.cbegin(); it != target.cend(); ++it) + { + if (source.find(it.key()) == source.end()) + { + // found a key that is not in this -> add it + const auto key = json_pointer::escape(it.key()); + result.push_back( + { + {"op", "add"}, {"path", path + "/" + key}, + {"value", it.value()} + }); + } + } + + break; + } + + default: + { + // both primitive type: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + break; + } + } + + return result; + } + + /// @} + + //////////////////////////////// + // JSON Merge Patch functions // + //////////////////////////////// + + /// @name JSON Merge Patch functions + /// @{ + + /*! + @brief applies a JSON Merge Patch + + The merge patch format is primarily intended for use with the HTTP PATCH + method as a means of describing a set of modifications to a target + resource's content. This function applies a merge patch to the current + JSON value. + + The function implements the following algorithm from Section 2 of + [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396): + + ``` + define MergePatch(Target, Patch): + if Patch is an Object: + if Target is not an Object: + Target = {} // Ignore the contents and set it to an empty Object + for each Name/Value pair in Patch: + if Value is null: + if Name exists in Target: + remove the Name/Value pair from Target + else: + Target[Name] = MergePatch(Target[Name], Value) + return Target + else: + return Patch + ``` + + Thereby, `Target` is the current object; that is, the patch is applied to + the current value. + + @param[in] apply_patch the patch to apply + + @complexity Linear in the lengths of @a patch. + + @liveexample{The following code shows how a JSON Merge Patch is applied to + a JSON document.,merge_patch} + + @sa @ref patch -- apply a JSON patch + @sa [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396) + + @since version 3.0.0 + */ + void merge_patch(const basic_json& apply_patch) + { + if (apply_patch.is_object()) + { + if (!is_object()) + { + *this = object(); + } + for (auto it = apply_patch.begin(); it != apply_patch.end(); ++it) + { + if (it.value().is_null()) + { + erase(it.key()); + } + else + { + operator[](it.key()).merge_patch(it.value()); + } + } + } + else + { + *this = apply_patch; + } + } + + /// @} +}; + +/*! +@brief user-defined to_string function for JSON values + +This function implements a user-defined to_string for JSON objects. + +@param[in] j a JSON object +@return a std::string object +*/ + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +std::string to_string(const NLOHMANN_BASIC_JSON_TPL& j) +{ + return j.dump(); +} +} // namespace nlohmann + +/////////////////////// +// nonmember support // +/////////////////////// + +// specialization of std::swap, and std::hash +namespace std +{ + +/// hash value for JSON objects +template<> +struct hash +{ + /*! + @brief return a hash value for a JSON object + + @since version 1.0.0 + */ + std::size_t operator()(const nlohmann::json& j) const + { + return nlohmann::detail::hash(j); + } +}; + +/// specialization for std::less +/// @note: do not remove the space after '<', +/// see https://github.com/nlohmann/json/pull/679 +template<> +struct less<::nlohmann::detail::value_t> +{ + /*! + @brief compare two value_t enum values + @since version 3.0.0 + */ + bool operator()(nlohmann::detail::value_t lhs, + nlohmann::detail::value_t rhs) const noexcept + { + return nlohmann::detail::operator<(lhs, rhs); + } +}; + +// C++20 prohibit function specialization in the std namespace. +#ifndef JSON_HAS_CPP_20 + +/*! +@brief exchanges the values of two JSON objects + +@since version 1.0.0 +*/ +template<> +inline void swap(nlohmann::json& j1, nlohmann::json& j2) noexcept( + is_nothrow_move_constructible::value&& + is_nothrow_move_assignable::value + ) +{ + j1.swap(j2); +} + +#endif + +} // namespace std + +/*! +@brief user-defined string literal for JSON values + +This operator implements a user-defined string literal for JSON objects. It +can be used by adding `"_json"` to a string literal and returns a JSON object +if no parse error occurred. + +@param[in] s a string representation of a JSON object +@param[in] n the length of string @a s +@return a JSON object + +@since version 1.0.0 +*/ +JSON_HEDLEY_NON_NULL(1) +inline nlohmann::json operator "" _json(const char* s, std::size_t n) +{ + return nlohmann::json::parse(s, s + n); +} + +/*! +@brief user-defined string literal for JSON pointer + +This operator implements a user-defined string literal for JSON Pointers. It +can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer +object if no parse error occurred. + +@param[in] s a string representation of a JSON Pointer +@param[in] n the length of string @a s +@return a JSON pointer object + +@since version 2.0.0 +*/ +JSON_HEDLEY_NON_NULL(1) +inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) +{ + return nlohmann::json::json_pointer(std::string(s, n)); +} + +// #include + + +// restore GCC/clang diagnostic settings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic pop +#endif +#if defined(__clang__) + #pragma GCC diagnostic pop +#endif + +// clean up +#undef JSON_ASSERT +#undef JSON_INTERNAL_CATCH +#undef JSON_CATCH +#undef JSON_THROW +#undef JSON_TRY +#undef JSON_HAS_CPP_14 +#undef JSON_HAS_CPP_17 +#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION +#undef NLOHMANN_BASIC_JSON_TPL +#undef JSON_EXPLICIT + +// #include +#undef JSON_HEDLEY_ALWAYS_INLINE +#undef JSON_HEDLEY_ARM_VERSION +#undef JSON_HEDLEY_ARM_VERSION_CHECK +#undef JSON_HEDLEY_ARRAY_PARAM +#undef JSON_HEDLEY_ASSUME +#undef JSON_HEDLEY_BEGIN_C_DECLS +#undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#undef JSON_HEDLEY_CLANG_HAS_FEATURE +#undef JSON_HEDLEY_CLANG_HAS_WARNING +#undef JSON_HEDLEY_COMPCERT_VERSION +#undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#undef JSON_HEDLEY_CONCAT +#undef JSON_HEDLEY_CONCAT3 +#undef JSON_HEDLEY_CONCAT3_EX +#undef JSON_HEDLEY_CONCAT_EX +#undef JSON_HEDLEY_CONST +#undef JSON_HEDLEY_CONSTEXPR +#undef JSON_HEDLEY_CONST_CAST +#undef JSON_HEDLEY_CPP_CAST +#undef JSON_HEDLEY_CRAY_VERSION +#undef JSON_HEDLEY_CRAY_VERSION_CHECK +#undef JSON_HEDLEY_C_DECL +#undef JSON_HEDLEY_DEPRECATED +#undef JSON_HEDLEY_DEPRECATED_FOR +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#undef JSON_HEDLEY_DIAGNOSTIC_POP +#undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#undef JSON_HEDLEY_DMC_VERSION +#undef JSON_HEDLEY_DMC_VERSION_CHECK +#undef JSON_HEDLEY_EMPTY_BASES +#undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#undef JSON_HEDLEY_END_C_DECLS +#undef JSON_HEDLEY_FLAGS +#undef JSON_HEDLEY_FLAGS_CAST +#undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#undef JSON_HEDLEY_GCC_HAS_BUILTIN +#undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_GCC_HAS_EXTENSION +#undef JSON_HEDLEY_GCC_HAS_FEATURE +#undef JSON_HEDLEY_GCC_HAS_WARNING +#undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#undef JSON_HEDLEY_GCC_VERSION +#undef JSON_HEDLEY_GCC_VERSION_CHECK +#undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#undef JSON_HEDLEY_GNUC_HAS_FEATURE +#undef JSON_HEDLEY_GNUC_HAS_WARNING +#undef JSON_HEDLEY_GNUC_VERSION +#undef JSON_HEDLEY_GNUC_VERSION_CHECK +#undef JSON_HEDLEY_HAS_ATTRIBUTE +#undef JSON_HEDLEY_HAS_BUILTIN +#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_HAS_EXTENSION +#undef JSON_HEDLEY_HAS_FEATURE +#undef JSON_HEDLEY_HAS_WARNING +#undef JSON_HEDLEY_IAR_VERSION +#undef JSON_HEDLEY_IAR_VERSION_CHECK +#undef JSON_HEDLEY_IBM_VERSION +#undef JSON_HEDLEY_IBM_VERSION_CHECK +#undef JSON_HEDLEY_IMPORT +#undef JSON_HEDLEY_INLINE +#undef JSON_HEDLEY_INTEL_VERSION +#undef JSON_HEDLEY_INTEL_VERSION_CHECK +#undef JSON_HEDLEY_IS_CONSTANT +#undef JSON_HEDLEY_IS_CONSTEXPR_ +#undef JSON_HEDLEY_LIKELY +#undef JSON_HEDLEY_MALLOC +#undef JSON_HEDLEY_MESSAGE +#undef JSON_HEDLEY_MSVC_VERSION +#undef JSON_HEDLEY_MSVC_VERSION_CHECK +#undef JSON_HEDLEY_NEVER_INLINE +#undef JSON_HEDLEY_NON_NULL +#undef JSON_HEDLEY_NO_ESCAPE +#undef JSON_HEDLEY_NO_RETURN +#undef JSON_HEDLEY_NO_THROW +#undef JSON_HEDLEY_NULL +#undef JSON_HEDLEY_PELLES_VERSION +#undef JSON_HEDLEY_PELLES_VERSION_CHECK +#undef JSON_HEDLEY_PGI_VERSION +#undef JSON_HEDLEY_PGI_VERSION_CHECK +#undef JSON_HEDLEY_PREDICT +#undef JSON_HEDLEY_PRINTF_FORMAT +#undef JSON_HEDLEY_PRIVATE +#undef JSON_HEDLEY_PUBLIC +#undef JSON_HEDLEY_PURE +#undef JSON_HEDLEY_REINTERPRET_CAST +#undef JSON_HEDLEY_REQUIRE +#undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#undef JSON_HEDLEY_REQUIRE_MSG +#undef JSON_HEDLEY_RESTRICT +#undef JSON_HEDLEY_RETURNS_NON_NULL +#undef JSON_HEDLEY_SENTINEL +#undef JSON_HEDLEY_STATIC_ASSERT +#undef JSON_HEDLEY_STATIC_CAST +#undef JSON_HEDLEY_STRINGIFY +#undef JSON_HEDLEY_STRINGIFY_EX +#undef JSON_HEDLEY_SUNPRO_VERSION +#undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#undef JSON_HEDLEY_TINYC_VERSION +#undef JSON_HEDLEY_TINYC_VERSION_CHECK +#undef JSON_HEDLEY_TI_ARMCL_VERSION +#undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL2000_VERSION +#undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL430_VERSION +#undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL6X_VERSION +#undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL7X_VERSION +#undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#undef JSON_HEDLEY_TI_CLPRU_VERSION +#undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#undef JSON_HEDLEY_TI_VERSION +#undef JSON_HEDLEY_TI_VERSION_CHECK +#undef JSON_HEDLEY_UNAVAILABLE +#undef JSON_HEDLEY_UNLIKELY +#undef JSON_HEDLEY_UNPREDICTABLE +#undef JSON_HEDLEY_UNREACHABLE +#undef JSON_HEDLEY_UNREACHABLE_RETURN +#undef JSON_HEDLEY_VERSION +#undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#undef JSON_HEDLEY_VERSION_DECODE_MINOR +#undef JSON_HEDLEY_VERSION_DECODE_REVISION +#undef JSON_HEDLEY_VERSION_ENCODE +#undef JSON_HEDLEY_WARNING +#undef JSON_HEDLEY_WARN_UNUSED_RESULT +#undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#undef JSON_HEDLEY_FALL_THROUGH + + + +#endif // INCLUDE_NLOHMANN_JSON_HPP_ diff --git a/src/jwt-cpp/include/jwt-cpp/picojson.h b/dep/jwt-cpp/include/picojson/picojson.h similarity index 96% rename from src/jwt-cpp/include/jwt-cpp/picojson.h rename to dep/jwt-cpp/include/picojson/picojson.h index 24a60c5be..76742fe06 100644 --- a/src/jwt-cpp/include/jwt-cpp/picojson.h +++ b/dep/jwt-cpp/include/picojson/picojson.h @@ -76,8 +76,14 @@ extern "C" { // experimental support for int64_t (see README.mkdn for detail) #ifdef PICOJSON_USE_INT64 #define __STDC_FORMAT_MACROS -#include +#include +#if __cplusplus >= 201103L +#include +#else +extern "C" { #include +} +#endif #endif // to disable the use of localeconv(3), set PICOJSON_USE_LOCALE to 0 @@ -104,6 +110,7 @@ extern "C" { #pragma warning(disable : 4244) // conversion from int to char #pragma warning(disable : 4127) // conditional expression is constant #pragma warning(disable : 4702) // unreachable code +#pragma warning(disable : 4706) // assignment within conditional expression #else #define SNPRINTF snprintf #endif @@ -123,7 +130,7 @@ enum { #endif }; -enum { INDENT_WIDTH = 2 }; +enum { INDENT_WIDTH = 2, DEFAULT_MAX_DEPTHS = 100 }; struct null {}; @@ -377,7 +384,7 @@ GET(array, *u_.array_) GET(object, *u_.object_) #ifdef PICOJSON_USE_INT64 GET(double, - (type_ == int64_type && (const_cast(this)->type_ = number_type, const_cast(this)->u_.number_ = u_.int64_), + (type_ == int64_type && (const_cast(this)->type_ = number_type, (const_cast(this)->u_.number_ = u_.int64_)), u_.number_)) GET(int64_t, u_.int64_) #else @@ -832,7 +839,7 @@ template inline bool _parse_object(Context &ct return false; } if (in.expect('}')) { - return true; + return ctx.parse_object_stop(); } do { std::string key; @@ -843,7 +850,7 @@ template inline bool _parse_object(Context &ct return false; } } while (in.expect(',')); - return in.expect('}'); + return in.expect('}') && ctx.parse_object_stop(); } template inline std::string _parse_number(input &in) { @@ -959,9 +966,10 @@ public: class default_parse_context { protected: value *out_; + size_t depths_; public: - default_parse_context(value *out) : out_(out) { + default_parse_context(value *out, size_t depths = DEFAULT_MAX_DEPTHS) : out_(out), depths_(depths) { } bool set_null() { *out_ = value(); @@ -986,27 +994,37 @@ public: return _parse_string(out_->get(), in); } bool parse_array_start() { + if (depths_ == 0) + return false; + --depths_; *out_ = value(array_type, false); return true; } template bool parse_array_item(input &in, size_t) { array &a = out_->get(); a.push_back(value()); - default_parse_context ctx(&a.back()); + default_parse_context ctx(&a.back(), depths_); return _parse(ctx, in); } bool parse_array_stop(size_t) { + ++depths_; return true; } bool parse_object_start() { + if (depths_ == 0) + return false; *out_ = value(object_type, false); return true; } template bool parse_object_item(input &in, const std::string &key) { object &o = out_->get(); - default_parse_context ctx(&o[key]); + default_parse_context ctx(&o[key], depths_); return _parse(ctx, in); } + bool parse_object_stop() { + ++depths_; + return true; + } private: default_parse_context(const default_parse_context &); @@ -1014,6 +1032,9 @@ private: }; class null_parse_context { +protected: + size_t depths_; + public: struct dummy_str { void push_back(int) { @@ -1021,7 +1042,7 @@ public: }; public: - null_parse_context() { + null_parse_context(size_t depths = DEFAULT_MAX_DEPTHS) : depths_(depths) { } bool set_null() { return true; @@ -1042,20 +1063,31 @@ public: return _parse_string(s, in); } bool parse_array_start() { + if (depths_ == 0) + return false; + --depths_; return true; } template bool parse_array_item(input &in, size_t) { return _parse(*this, in); } bool parse_array_stop(size_t) { + ++depths_; return true; } bool parse_object_start() { + if (depths_ == 0) + return false; + --depths_; return true; } template bool parse_object_item(input &in, const std::string &) { + ++depths_; return _parse(*this, in); } + bool parse_object_stop() { + return true; + } private: null_parse_context(const null_parse_context &); @@ -1165,4 +1197,4 @@ inline std::ostream &operator<<(std::ostream &os, const picojson::value &x) { #pragma warning(pop) #endif -#endif \ No newline at end of file +#endif diff --git a/dep/libbcrypt/CMakeLists.txt b/dep/libbcrypt/CMakeLists.txt new file mode 100644 index 000000000..9885d016d --- /dev/null +++ b/dep/libbcrypt/CMakeLists.txt @@ -0,0 +1,25 @@ +enable_language(ASM) + +set(BCRYPT_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/src/bcrypt.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/crypt_blowfish.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/crypt_gensalt.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/wrapper.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/x86.S) + +add_library(bcrypt STATIC ${BCRYPT_SOURCES}) +add_library(libbcrypt::bcrypt ALIAS bcrypt) + +target_include_directories(bcrypt + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include/bcrypt) + +target_link_libraries(bcrypt + PRIVATE + zm-dependency-interface) + +if(BSD) + target_compile_definitions(bcrypt + PRIVATE + __SKIP_GNU) +endif() diff --git a/src/libbcrypt/LICENSE b/dep/libbcrypt/LICENSE similarity index 100% rename from src/libbcrypt/LICENSE rename to dep/libbcrypt/LICENSE diff --git a/src/libbcrypt/README.md b/dep/libbcrypt/README.md similarity index 100% rename from src/libbcrypt/README.md rename to dep/libbcrypt/README.md diff --git a/src/libbcrypt/include/bcrypt/BCrypt.hpp b/dep/libbcrypt/include/bcrypt/BCrypt.hpp similarity index 96% rename from src/libbcrypt/include/bcrypt/BCrypt.hpp rename to dep/libbcrypt/include/bcrypt/BCrypt.hpp index 27f9b563e..07e5c53d4 100644 --- a/src/libbcrypt/include/bcrypt/BCrypt.hpp +++ b/dep/libbcrypt/include/bcrypt/BCrypt.hpp @@ -1,32 +1,32 @@ -#ifndef __BCRYPT__ -#define __BCRYPT__ - -#ifdef _WIN32 -#include "winbcrypt.h" -#else - -#include "bcrypt.h" -#include -#include - -class BCrypt { -public: - static std::string generateHash(const std::string & password, int workload = 12){ - char salt[BCRYPT_HASHSIZE]; - char hash[BCRYPT_HASHSIZE]; - int ret; - ret = bcrypt_gensalt(workload, salt); - if(ret != 0)throw std::runtime_error{"bcrypt: can not generate salt"}; - ret = bcrypt_hashpw(password.c_str(), salt, hash); - if(ret != 0)throw std::runtime_error{"bcrypt: can not generate hash"}; - return std::string{hash}; - } - - static bool validatePassword(const std::string & password, const std::string & hash){ - return (bcrypt_checkpw(password.c_str(), hash.c_str()) == 0); - } -}; - -#endif - -#endif // __BCRYPT__ +#ifndef __BCRYPT__ +#define __BCRYPT__ + +#ifdef _WIN32 +#include "winbcrypt.h" +#else + +#include "bcrypt.h" +#include +#include + +class BCrypt { +public: + static std::string generateHash(const std::string & password, int workload = 12){ + char salt[BCRYPT_HASHSIZE]; + char hash[BCRYPT_HASHSIZE]; + int ret; + ret = bcrypt_gensalt(workload, salt); + if(ret != 0)throw std::runtime_error{"bcrypt: can not generate salt"}; + ret = bcrypt_hashpw(password.c_str(), salt, hash); + if(ret != 0)throw std::runtime_error{"bcrypt: can not generate hash"}; + return std::string{hash}; + } + + static bool validatePassword(const std::string & password, const std::string & hash){ + return (bcrypt_checkpw(password.c_str(), hash.c_str()) == 0); + } +}; + +#endif + +#endif // __BCRYPT__ diff --git a/src/libbcrypt/include/bcrypt/bcrypt.h b/dep/libbcrypt/include/bcrypt/bcrypt.h similarity index 100% rename from src/libbcrypt/include/bcrypt/bcrypt.h rename to dep/libbcrypt/include/bcrypt/bcrypt.h diff --git a/src/libbcrypt/include/bcrypt/crypt.h b/dep/libbcrypt/include/bcrypt/crypt.h similarity index 100% rename from src/libbcrypt/include/bcrypt/crypt.h rename to dep/libbcrypt/include/bcrypt/crypt.h diff --git a/src/libbcrypt/include/bcrypt/crypt_blowfish.h b/dep/libbcrypt/include/bcrypt/crypt_blowfish.h similarity index 100% rename from src/libbcrypt/include/bcrypt/crypt_blowfish.h rename to dep/libbcrypt/include/bcrypt/crypt_blowfish.h diff --git a/src/libbcrypt/include/bcrypt/crypt_gensalt.h b/dep/libbcrypt/include/bcrypt/crypt_gensalt.h similarity index 100% rename from src/libbcrypt/include/bcrypt/crypt_gensalt.h rename to dep/libbcrypt/include/bcrypt/crypt_gensalt.h diff --git a/src/libbcrypt/include/bcrypt/ow-crypt.h b/dep/libbcrypt/include/bcrypt/ow-crypt.h similarity index 97% rename from src/libbcrypt/include/bcrypt/ow-crypt.h rename to dep/libbcrypt/include/bcrypt/ow-crypt.h index 2c8ddf8f6..2e4879426 100644 --- a/src/libbcrypt/include/bcrypt/ow-crypt.h +++ b/dep/libbcrypt/include/bcrypt/ow-crypt.h @@ -1,43 +1,43 @@ -/* - * Written by Solar Designer in 2000-2011. - * No copyright is claimed, and the software is hereby placed in the public - * domain. In case this attempt to disclaim copyright and place the software - * in the public domain is deemed null and void, then the software is - * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the - * general public under the following terms: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted. - * - * There's ABSOLUTELY NO WARRANTY, express or implied. - * - * See crypt_blowfish.c for more information. - */ - -#ifndef _OW_CRYPT_H -#define _OW_CRYPT_H - -#ifndef __GNUC__ -#undef __const -#define __const const -#endif - -#ifndef __SKIP_GNU -extern char *crypt(__const char *key, __const char *setting); -extern char *crypt_r(__const char *key, __const char *setting, void *data); -#endif - -#ifndef __SKIP_OW -extern char *crypt_rn(__const char *key, __const char *setting, - void *data, int size); -extern char *crypt_ra(__const char *key, __const char *setting, - void **data, int *size); -extern char *crypt_gensalt(__const char *prefix, unsigned long count, - __const char *input, int size); -extern char *crypt_gensalt_rn(__const char *prefix, unsigned long count, - __const char *input, int size, char *output, int output_size); -extern char *crypt_gensalt_ra(__const char *prefix, unsigned long count, - __const char *input, int size); -#endif - -#endif +/* + * Written by Solar Designer in 2000-2011. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * See crypt_blowfish.c for more information. + */ + +#ifndef _OW_CRYPT_H +#define _OW_CRYPT_H + +#ifndef __GNUC__ +#undef __const +#define __const const +#endif + +#ifndef __SKIP_GNU +extern char *crypt(__const char *key, __const char *setting); +extern char *crypt_r(__const char *key, __const char *setting, void *data); +#endif + +#ifndef __SKIP_OW +extern char *crypt_rn(__const char *key, __const char *setting, + void *data, int size); +extern char *crypt_ra(__const char *key, __const char *setting, + void **data, int *size); +extern char *crypt_gensalt(__const char *prefix, unsigned long count, + __const char *input, int size); +extern char *crypt_gensalt_rn(__const char *prefix, unsigned long count, + __const char *input, int size, char *output, int output_size); +extern char *crypt_gensalt_ra(__const char *prefix, unsigned long count, + __const char *input, int size); +#endif + +#endif diff --git a/src/libbcrypt/include/bcrypt/winbcrypt.h b/dep/libbcrypt/include/bcrypt/winbcrypt.h similarity index 95% rename from src/libbcrypt/include/bcrypt/winbcrypt.h rename to dep/libbcrypt/include/bcrypt/winbcrypt.h index 703ecd211..8ac27e1c3 100644 --- a/src/libbcrypt/include/bcrypt/winbcrypt.h +++ b/dep/libbcrypt/include/bcrypt/winbcrypt.h @@ -1,27 +1,27 @@ -#ifndef __WIN_BCRYPT__H -#define __WIN_BCRYPT__H - -#include - -#include "crypt_blowfish.h" -#include "./bcrypt.h" - -class BCrypt { -public: - static std::string generateHash(const std::string & password, int workload = 12) { - char salt[BCRYPT_HASHSIZE]; - char hash[BCRYPT_HASHSIZE]; - int ret; - ret = bcrypt_gensalt(workload, salt); - if (ret != 0)throw std::runtime_error{ "bcrypt: can not generate salt" }; - ret = bcrypt_hashpw(password.c_str(), salt, hash); - if (ret != 0)throw std::runtime_error{ "bcrypt: can not generate hash" }; - return std::string{ hash }; - } - - static bool validatePassword(const std::string & password, const std::string & hash) { - return (bcrypt_checkpw(password.c_str(), hash.c_str()) == 0); - } -}; - +#ifndef __WIN_BCRYPT__H +#define __WIN_BCRYPT__H + +#include + +#include "crypt_blowfish.h" +#include "./bcrypt.h" + +class BCrypt { +public: + static std::string generateHash(const std::string & password, int workload = 12) { + char salt[BCRYPT_HASHSIZE]; + char hash[BCRYPT_HASHSIZE]; + int ret; + ret = bcrypt_gensalt(workload, salt); + if (ret != 0)throw std::runtime_error{ "bcrypt: can not generate salt" }; + ret = bcrypt_hashpw(password.c_str(), salt, hash); + if (ret != 0)throw std::runtime_error{ "bcrypt: can not generate hash" }; + return std::string{ hash }; + } + + static bool validatePassword(const std::string & password, const std::string & hash) { + return (bcrypt_checkpw(password.c_str(), hash.c_str()) == 0); + } +}; + #endif \ No newline at end of file diff --git a/src/libbcrypt/src/bcrypt.c b/dep/libbcrypt/src/bcrypt.c similarity index 94% rename from src/libbcrypt/src/bcrypt.c rename to dep/libbcrypt/src/bcrypt.c index c32b51b0b..eafdb2136 100644 --- a/src/libbcrypt/src/bcrypt.c +++ b/dep/libbcrypt/src/bcrypt.c @@ -1,230 +1,230 @@ -/* - * bcrypt wrapper library - * - * Written in 2011, 2013, 2014, 2015 by Ricardo Garcia - * - * To the extent possible under law, the author(s) have dedicated all copyright - * and related and neighboring rights to this software to the public domain - * worldwide. This software is distributed without any warranty. - * - * You should have received a copy of the CC0 Public Domain Dedication along - * with this software. If not, see - * . - */ -#include -#include -#include -#include -#ifdef _WIN32 -#elif _WIN64 -#else -#include -#endif -#include - -#ifdef _WIN32 || _WIN64 -// On windows we need to generate random bytes differently. -typedef __int64 ssize_t; -#define BCRYPT_HASHSIZE 60 - -#include "../include/bcrypt/bcrypt.h" - -#include -#include /* CryptAcquireContext, CryptGenRandom */ -#else -#include "bcrypt.h" -#include "ow-crypt.h" -#endif - -#define RANDBYTES (16) - -static int try_close(int fd) -{ - int ret; - for (;;) { - errno = 0; - ret = close(fd); - if (ret == -1 && errno == EINTR) - continue; - break; - } - return ret; -} - -static int try_read(int fd, char *out, size_t count) -{ - size_t total; - ssize_t partial; - - total = 0; - while (total < count) - { - for (;;) { - errno = 0; - partial = read(fd, out + total, count - total); - if (partial == -1 && errno == EINTR) - continue; - break; - } - - if (partial < 1) - return -1; - - total += partial; - } - - return 0; -} - -/* - * This is a best effort implementation. Nothing prevents a compiler from - * optimizing this function and making it vulnerable to timing attacks, but - * this method is commonly used in crypto libraries like NaCl. - * - * Return value is zero if both strings are equal and nonzero otherwise. -*/ -static int timing_safe_strcmp(const char *str1, const char *str2) -{ - const unsigned char *u1; - const unsigned char *u2; - int ret; - int i; - - int len1 = strlen(str1); - int len2 = strlen(str2); - - /* In our context both strings should always have the same length - * because they will be hashed passwords. */ - if (len1 != len2) - return 1; - - /* Force unsigned for bitwise operations. */ - u1 = (const unsigned char *)str1; - u2 = (const unsigned char *)str2; - - ret = 0; - for (i = 0; i < len1; ++i) - ret |= (u1[i] ^ u2[i]); - - return ret; -} - -int bcrypt_gensalt(int factor, char salt[BCRYPT_HASHSIZE]) -{ - int fd; - char input[RANDBYTES]; - int workf; - char *aux; - - // Note: Windows does not have /dev/urandom sadly. -#ifdef _WIN32 || _WIN64 - HCRYPTPROV p; - ULONG i; - - // Acquire a crypt context for generating random bytes. - if (CryptAcquireContext(&p, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) == FALSE) { - return 1; - } - - if (CryptGenRandom(p, RANDBYTES, (BYTE*)input) == FALSE) { - return 2; - } - - if (CryptReleaseContext(p, 0) == FALSE) { - return 3; - } -#else - // Get random bytes on Unix/Linux. - fd = open("/dev/urandom", O_RDONLY); - if (fd == -1) - return 1; - - if (try_read(fd, input, RANDBYTES) != 0) { - if (try_close(fd) != 0) - return 4; - return 2; - } - - if (try_close(fd) != 0) - return 3; -#endif - - /* Generate salt. */ - workf = (factor < 4 || factor > 31)?12:factor; - aux = crypt_gensalt_rn("$2a$", workf, input, RANDBYTES, - salt, BCRYPT_HASHSIZE); - return (aux == NULL)?5:0; -} - -int bcrypt_hashpw(const char *passwd, const char salt[BCRYPT_HASHSIZE], char hash[BCRYPT_HASHSIZE]) -{ - char *aux; - aux = crypt_rn(passwd, salt, hash, BCRYPT_HASHSIZE); - return (aux == NULL)?1:0; -} - -int bcrypt_checkpw(const char *passwd, const char hash[BCRYPT_HASHSIZE]) -{ - int ret; - char outhash[BCRYPT_HASHSIZE]; - - ret = bcrypt_hashpw(passwd, hash, outhash); - if (ret != 0) - return -1; - - return timing_safe_strcmp(hash, outhash); -} - -#ifdef TEST_BCRYPT -#include -#include -#include -#include - -int main(void) -{ - clock_t before; - clock_t after; - char salt[BCRYPT_HASHSIZE]; - char hash[BCRYPT_HASHSIZE]; - int ret; - - const char pass[] = "hi,mom"; - const char hash1[] = "$2a$10$VEVmGHy4F4XQMJ3eOZJAUeb.MedU0W10pTPCuf53eHdKJPiSE8sMK"; - const char hash2[] = "$2a$10$3F0BVk5t8/aoS.3ddaB3l.fxg5qvafQ9NybxcpXLzMeAt.nVWn.NO"; - - ret = bcrypt_gensalt(12, salt); - assert(ret == 0); - printf("Generated salt: %s\n", salt); - before = clock(); - ret = bcrypt_hashpw("testtesttest", salt, hash); - assert(ret == 0); - after = clock(); - printf("Hashed password: %s\n", hash); - printf("Time taken: %f seconds\n", - (double)(after - before) / CLOCKS_PER_SEC); - - ret = bcrypt_hashpw(pass, hash1, hash); - assert(ret == 0); - printf("First hash check: %s\n", (strcmp(hash1, hash) == 0)?"OK":"FAIL"); - ret = bcrypt_hashpw(pass, hash2, hash); - assert(ret == 0); - printf("Second hash check: %s\n", (strcmp(hash2, hash) == 0)?"OK":"FAIL"); - - before = clock(); - ret = (bcrypt_checkpw(pass, hash1) == 0); - after = clock(); - printf("First hash check with bcrypt_checkpw: %s\n", ret?"OK":"FAIL"); - printf("Time taken: %f seconds\n", - (double)(after - before) / CLOCKS_PER_SEC); - - before = clock(); - ret = (bcrypt_checkpw(pass, hash2) == 0); - after = clock(); - printf("Second hash check with bcrypt_checkpw: %s\n", ret?"OK":"FAIL"); - printf("Time taken: %f seconds\n", - (double)(after - before) / CLOCKS_PER_SEC); - - return 0; -} -#endif +/* + * bcrypt wrapper library + * + * Written in 2011, 2013, 2014, 2015 by Ricardo Garcia + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ +#include +#include +#include +#include +#ifdef _WIN32 +#elif _WIN64 +#else +#include +#endif +#include + +#if defined(_WIN32) || defined(_WIN64) +// On windows we need to generate random bytes differently. +typedef __int64 ssize_t; +#define BCRYPT_HASHSIZE 60 + +#include "../include/bcrypt/bcrypt.h" + +#include +#include /* CryptAcquireContext, CryptGenRandom */ +#else +#include "bcrypt.h" +#include "ow-crypt.h" +#endif + +#define RANDBYTES (16) + +static int try_close(int fd) +{ + int ret; + for (;;) { + errno = 0; + ret = close(fd); + if (ret == -1 && errno == EINTR) + continue; + break; + } + return ret; +} + +static int try_read(int fd, char *out, size_t count) +{ + size_t total; + ssize_t partial; + + total = 0; + while (total < count) + { + for (;;) { + errno = 0; + partial = read(fd, out + total, count - total); + if (partial == -1 && errno == EINTR) + continue; + break; + } + + if (partial < 1) + return -1; + + total += partial; + } + + return 0; +} + +/* + * This is a best effort implementation. Nothing prevents a compiler from + * optimizing this function and making it vulnerable to timing attacks, but + * this method is commonly used in crypto libraries like NaCl. + * + * Return value is zero if both strings are equal and nonzero otherwise. +*/ +static int timing_safe_strcmp(const char *str1, const char *str2) +{ + const unsigned char *u1; + const unsigned char *u2; + int ret; + int i; + + int len1 = strlen(str1); + int len2 = strlen(str2); + + /* In our context both strings should always have the same length + * because they will be hashed passwords. */ + if (len1 != len2) + return 1; + + /* Force unsigned for bitwise operations. */ + u1 = (const unsigned char *)str1; + u2 = (const unsigned char *)str2; + + ret = 0; + for (i = 0; i < len1; ++i) + ret |= (u1[i] ^ u2[i]); + + return ret; +} + +int bcrypt_gensalt(int factor, char salt[BCRYPT_HASHSIZE]) +{ + int fd; + char input[RANDBYTES]; + int workf; + char *aux; + + // Note: Windows does not have /dev/urandom sadly. +#if defined(_WIN32) || defined(_WIN64) + HCRYPTPROV p; + ULONG i; + + // Acquire a crypt context for generating random bytes. + if (CryptAcquireContext(&p, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) == FALSE) { + return 1; + } + + if (CryptGenRandom(p, RANDBYTES, (BYTE*)input) == FALSE) { + return 2; + } + + if (CryptReleaseContext(p, 0) == FALSE) { + return 3; + } +#else + // Get random bytes on Unix/Linux. + fd = open("/dev/urandom", O_RDONLY); + if (fd == -1) + return 1; + + if (try_read(fd, input, RANDBYTES) != 0) { + if (try_close(fd) != 0) + return 4; + return 2; + } + + if (try_close(fd) != 0) + return 3; +#endif + + /* Generate salt. */ + workf = (factor < 4 || factor > 31)?12:factor; + aux = crypt_gensalt_rn("$2a$", workf, input, RANDBYTES, + salt, BCRYPT_HASHSIZE); + return (aux == NULL)?5:0; +} + +int bcrypt_hashpw(const char *passwd, const char salt[BCRYPT_HASHSIZE], char hash[BCRYPT_HASHSIZE]) +{ + char *aux; + aux = crypt_rn(passwd, salt, hash, BCRYPT_HASHSIZE); + return (aux == NULL)?1:0; +} + +int bcrypt_checkpw(const char *passwd, const char hash[BCRYPT_HASHSIZE]) +{ + int ret; + char outhash[BCRYPT_HASHSIZE]; + + ret = bcrypt_hashpw(passwd, hash, outhash); + if (ret != 0) + return -1; + + return timing_safe_strcmp(hash, outhash); +} + +#ifdef TEST_BCRYPT +#include +#include +#include +#include + +int main(void) +{ + clock_t before; + clock_t after; + char salt[BCRYPT_HASHSIZE]; + char hash[BCRYPT_HASHSIZE]; + int ret; + + const char pass[] = "hi,mom"; + const char hash1[] = "$2a$10$VEVmGHy4F4XQMJ3eOZJAUeb.MedU0W10pTPCuf53eHdKJPiSE8sMK"; + const char hash2[] = "$2a$10$3F0BVk5t8/aoS.3ddaB3l.fxg5qvafQ9NybxcpXLzMeAt.nVWn.NO"; + + ret = bcrypt_gensalt(12, salt); + assert(ret == 0); + printf("Generated salt: %s\n", salt); + before = clock(); + ret = bcrypt_hashpw("testtesttest", salt, hash); + assert(ret == 0); + after = clock(); + printf("Hashed password: %s\n", hash); + printf("Time taken: %f seconds\n", + (double)(after - before) / CLOCKS_PER_SEC); + + ret = bcrypt_hashpw(pass, hash1, hash); + assert(ret == 0); + printf("First hash check: %s\n", (strcmp(hash1, hash) == 0)?"OK":"FAIL"); + ret = bcrypt_hashpw(pass, hash2, hash); + assert(ret == 0); + printf("Second hash check: %s\n", (strcmp(hash2, hash) == 0)?"OK":"FAIL"); + + before = clock(); + ret = (bcrypt_checkpw(pass, hash1) == 0); + after = clock(); + printf("First hash check with bcrypt_checkpw: %s\n", ret?"OK":"FAIL"); + printf("Time taken: %f seconds\n", + (double)(after - before) / CLOCKS_PER_SEC); + + before = clock(); + ret = (bcrypt_checkpw(pass, hash2) == 0); + after = clock(); + printf("Second hash check with bcrypt_checkpw: %s\n", ret?"OK":"FAIL"); + printf("Time taken: %f seconds\n", + (double)(after - before) / CLOCKS_PER_SEC); + + return 0; +} +#endif diff --git a/src/libbcrypt/src/crypt_blowfish.c b/dep/libbcrypt/src/crypt_blowfish.c similarity index 97% rename from src/libbcrypt/src/crypt_blowfish.c rename to dep/libbcrypt/src/crypt_blowfish.c index 0fa55ea07..27133d535 100644 --- a/src/libbcrypt/src/crypt_blowfish.c +++ b/dep/libbcrypt/src/crypt_blowfish.c @@ -1,911 +1,911 @@ -/* - * The crypt_blowfish homepage is: - * - * http://www.openwall.com/crypt/ - * - * This code comes from John the Ripper password cracker, with reentrant - * and crypt(3) interfaces added, but optimizations specific to password - * cracking removed. - * - * Written by Solar Designer in 1998-2014. - * No copyright is claimed, and the software is hereby placed in the public - * domain. In case this attempt to disclaim copyright and place the software - * in the public domain is deemed null and void, then the software is - * Copyright (c) 1998-2014 Solar Designer and it is hereby released to the - * general public under the following terms: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted. - * - * There's ABSOLUTELY NO WARRANTY, express or implied. - * - * It is my intent that you should be able to use this on your system, - * as part of a software package, or anywhere else to improve security, - * ensure compatibility, or for any other purpose. I would appreciate - * it if you give credit where it is due and keep your modifications in - * the public domain as well, but I don't require that in order to let - * you place this code and any modifications you make under a license - * of your choice. - * - * This implementation is fully compatible with OpenBSD's bcrypt.c for prefix - * "$2b$", originally by Niels Provos , and it uses - * some of his ideas. The password hashing algorithm was designed by David - * Mazieres . For information on the level of - * compatibility for bcrypt hash prefixes other than "$2b$", please refer to - * the comments in BF_set_key() below and to the included crypt(3) man page. - * - * There's a paper on the algorithm that explains its design decisions: - * - * http://www.usenix.org/events/usenix99/provos.html - * - * Some of the tricks in BF_ROUND might be inspired by Eric Young's - * Blowfish library (I can't be sure if I would think of something if I - * hadn't seen his code). - */ - -#include - -#include -#ifndef __set_errno -#define __set_errno(val) errno = (val) -#endif - -/* Just to make sure the prototypes match the actual definitions */ -#ifdef _WIN32 || _WIN64 -#include "../include/bcrypt/crypt_blowfish.h" -#else -#include "crypt_blowfish.h" -#endif - -#ifdef __i386__ -#define BF_ASM 1 -#define BF_SCALE 1 -#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__) -#define BF_ASM 0 -#define BF_SCALE 1 -#else -#define BF_ASM 0 -#define BF_SCALE 0 -#endif - -typedef unsigned int BF_word; -typedef signed int BF_word_signed; - -/* Number of Blowfish rounds, this is also hardcoded into a few places */ -#define BF_N 16 - -typedef BF_word BF_key[BF_N + 2]; - -typedef struct { - BF_word S[4][0x100]; - BF_key P; -} BF_ctx; - -/* - * Magic IV for 64 Blowfish encryptions that we do at the end. - * The string is "OrpheanBeholderScryDoubt" on big-endian. - */ -static BF_word BF_magic_w[6] = { - 0x4F727068, 0x65616E42, 0x65686F6C, - 0x64657253, 0x63727944, 0x6F756274 -}; - -/* - * P-box and S-box tables initialized with digits of Pi. - */ -static BF_ctx BF_init_state = { - { - { - 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, - 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, - 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, - 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, - 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, - 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, - 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, - 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, - 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, - 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, - 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, - 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, - 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, - 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, - 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, - 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, - 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, - 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, - 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, - 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, - 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, - 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, - 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, - 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, - 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, - 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, - 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, - 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, - 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, - 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, - 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, - 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, - 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, - 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, - 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, - 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, - 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, - 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, - 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, - 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, - 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, - 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, - 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, - 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, - 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, - 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, - 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, - 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, - 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, - 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, - 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, - 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, - 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, - 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, - 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, - 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, - 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, - 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, - 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, - 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, - 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, - 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, - 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, - 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a - }, { - 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, - 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, - 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, - 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, - 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, - 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, - 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, - 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, - 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, - 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, - 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, - 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, - 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, - 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, - 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, - 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, - 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, - 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, - 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, - 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, - 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, - 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, - 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, - 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, - 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, - 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, - 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, - 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, - 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, - 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, - 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, - 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, - 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, - 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, - 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, - 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, - 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, - 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, - 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, - 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, - 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, - 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, - 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, - 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, - 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, - 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, - 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, - 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, - 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, - 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, - 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, - 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, - 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, - 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, - 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, - 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, - 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, - 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, - 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, - 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, - 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, - 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, - 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, - 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 - }, { - 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, - 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, - 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, - 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, - 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, - 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, - 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, - 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, - 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, - 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, - 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, - 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, - 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, - 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, - 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, - 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, - 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, - 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, - 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, - 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, - 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, - 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, - 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, - 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, - 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, - 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, - 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, - 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, - 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, - 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, - 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, - 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, - 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, - 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, - 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, - 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, - 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, - 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, - 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, - 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, - 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, - 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, - 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, - 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, - 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, - 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, - 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, - 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, - 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, - 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, - 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, - 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, - 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, - 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, - 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, - 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, - 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, - 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, - 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, - 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, - 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, - 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, - 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, - 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 - }, { - 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, - 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, - 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, - 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, - 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, - 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, - 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, - 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, - 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, - 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, - 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, - 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, - 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, - 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, - 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, - 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, - 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, - 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, - 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, - 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, - 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, - 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, - 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, - 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, - 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, - 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, - 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, - 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, - 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, - 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, - 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, - 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, - 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, - 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, - 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, - 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, - 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, - 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, - 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, - 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, - 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, - 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, - 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, - 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, - 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, - 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, - 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, - 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, - 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, - 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, - 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, - 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, - 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, - 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, - 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, - 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, - 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, - 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, - 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, - 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, - 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, - 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, - 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, - 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 - } - }, { - 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, - 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, - 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, - 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, - 0x9216d5d9, 0x8979fb1b - } -}; - -static unsigned char BF_itoa64[64 + 1] = - "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - -static unsigned char BF_atoi64[0x60] = { - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, - 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64, - 64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 64, 64, 64, 64, 64, - 64, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, - 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64 -}; - -#define BF_safe_atoi64(dst, src) \ -{ \ - tmp = (unsigned char)(src); \ - if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ - tmp = BF_atoi64[tmp]; \ - if (tmp > 63) return -1; \ - (dst) = tmp; \ -} - -static int BF_decode(BF_word *dst, const char *src, int size) -{ - unsigned char *dptr = (unsigned char *)dst; - unsigned char *end = dptr + size; - const unsigned char *sptr = (const unsigned char *)src; - unsigned int tmp, c1, c2, c3, c4; - - do { - BF_safe_atoi64(c1, *sptr++); - BF_safe_atoi64(c2, *sptr++); - *dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4); - if (dptr >= end) break; - - BF_safe_atoi64(c3, *sptr++); - *dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2); - if (dptr >= end) break; - - BF_safe_atoi64(c4, *sptr++); - *dptr++ = ((c3 & 0x03) << 6) | c4; - } while (dptr < end); - - return 0; -} - -static void BF_encode(char *dst, const BF_word *src, int size) -{ - const unsigned char *sptr = (const unsigned char *)src; - const unsigned char *end = sptr + size; - unsigned char *dptr = (unsigned char *)dst; - unsigned int c1, c2; - - do { - c1 = *sptr++; - *dptr++ = BF_itoa64[c1 >> 2]; - c1 = (c1 & 0x03) << 4; - if (sptr >= end) { - *dptr++ = BF_itoa64[c1]; - break; - } - - c2 = *sptr++; - c1 |= c2 >> 4; - *dptr++ = BF_itoa64[c1]; - c1 = (c2 & 0x0f) << 2; - if (sptr >= end) { - *dptr++ = BF_itoa64[c1]; - break; - } - - c2 = *sptr++; - c1 |= c2 >> 6; - *dptr++ = BF_itoa64[c1]; - *dptr++ = BF_itoa64[c2 & 0x3f]; - } while (sptr < end); -} - -static void BF_swap(BF_word *x, int count) -{ - static int endianness_check = 1; - char *is_little_endian = (char *)&endianness_check; - BF_word tmp; - - if (*is_little_endian) - do { - tmp = *x; - tmp = (tmp << 16) | (tmp >> 16); - *x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF); - } while (--count); -} - -#if BF_SCALE -/* Architectures which can shift addresses left by 2 bits with no extra cost */ -#define BF_ROUND(L, R, N) \ - tmp1 = L & 0xFF; \ - tmp2 = L >> 8; \ - tmp2 &= 0xFF; \ - tmp3 = L >> 16; \ - tmp3 &= 0xFF; \ - tmp4 = L >> 24; \ - tmp1 = data.ctx.S[3][tmp1]; \ - tmp2 = data.ctx.S[2][tmp2]; \ - tmp3 = data.ctx.S[1][tmp3]; \ - tmp3 += data.ctx.S[0][tmp4]; \ - tmp3 ^= tmp2; \ - R ^= data.ctx.P[N + 1]; \ - tmp3 += tmp1; \ - R ^= tmp3; -#else -/* Architectures with no complicated addressing modes supported */ -#define BF_INDEX(S, i) \ - (*((BF_word *)(((unsigned char *)S) + (i)))) -#define BF_ROUND(L, R, N) \ - tmp1 = L & 0xFF; \ - tmp1 <<= 2; \ - tmp2 = L >> 6; \ - tmp2 &= 0x3FC; \ - tmp3 = L >> 14; \ - tmp3 &= 0x3FC; \ - tmp4 = L >> 22; \ - tmp4 &= 0x3FC; \ - tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \ - tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \ - tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \ - tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \ - tmp3 ^= tmp2; \ - R ^= data.ctx.P[N + 1]; \ - tmp3 += tmp1; \ - R ^= tmp3; -#endif - -/* - * Encrypt one block, BF_N is hardcoded here. - */ -#define BF_ENCRYPT \ - L ^= data.ctx.P[0]; \ - BF_ROUND(L, R, 0); \ - BF_ROUND(R, L, 1); \ - BF_ROUND(L, R, 2); \ - BF_ROUND(R, L, 3); \ - BF_ROUND(L, R, 4); \ - BF_ROUND(R, L, 5); \ - BF_ROUND(L, R, 6); \ - BF_ROUND(R, L, 7); \ - BF_ROUND(L, R, 8); \ - BF_ROUND(R, L, 9); \ - BF_ROUND(L, R, 10); \ - BF_ROUND(R, L, 11); \ - BF_ROUND(L, R, 12); \ - BF_ROUND(R, L, 13); \ - BF_ROUND(L, R, 14); \ - BF_ROUND(R, L, 15); \ - tmp4 = R; \ - R = L; \ - L = tmp4 ^ data.ctx.P[BF_N + 1]; - -#if BF_ASM -#define BF_body() \ - _BF_body_r(&data.ctx); -#else -#define BF_body() \ - L = R = 0; \ - ptr = data.ctx.P; \ - do { \ - ptr += 2; \ - BF_ENCRYPT; \ - *(ptr - 2) = L; \ - *(ptr - 1) = R; \ - } while (ptr < &data.ctx.P[BF_N + 2]); \ -\ - ptr = data.ctx.S[0]; \ - do { \ - ptr += 2; \ - BF_ENCRYPT; \ - *(ptr - 2) = L; \ - *(ptr - 1) = R; \ - } while (ptr < &data.ctx.S[3][0xFF]); -#endif - -static void BF_set_key(const char *key, BF_key expanded, BF_key initial, - unsigned char flags) -{ - const char *ptr = key; - unsigned int bug, i, j; - BF_word safety, sign, diff, tmp[2]; - -/* - * There was a sign extension bug in older revisions of this function. While - * we would have liked to simply fix the bug and move on, we have to provide - * a backwards compatibility feature (essentially the bug) for some systems and - * a safety measure for some others. The latter is needed because for certain - * multiple inputs to the buggy algorithm there exist easily found inputs to - * the correct algorithm that produce the same hash. Thus, we optionally - * deviate from the correct algorithm just enough to avoid such collisions. - * While the bug itself affected the majority of passwords containing - * characters with the 8th bit set (although only a percentage of those in a - * collision-producing way), the anti-collision safety measure affects - * only a subset of passwords containing the '\xff' character (not even all of - * those passwords, just some of them). This character is not found in valid - * UTF-8 sequences and is rarely used in popular 8-bit character encodings. - * Thus, the safety measure is unlikely to cause much annoyance, and is a - * reasonable tradeoff to use when authenticating against existing hashes that - * are not reliably known to have been computed with the correct algorithm. - * - * We use an approach that tries to minimize side-channel leaks of password - * information - that is, we mostly use fixed-cost bitwise operations instead - * of branches or table lookups. (One conditional branch based on password - * length remains. It is not part of the bug aftermath, though, and is - * difficult and possibly unreasonable to avoid given the use of C strings by - * the caller, which results in similar timing leaks anyway.) - * - * For actual implementation, we set an array index in the variable "bug" - * (0 means no bug, 1 means sign extension bug emulation) and a flag in the - * variable "safety" (bit 16 is set when the safety measure is requested). - * Valid combinations of settings are: - * - * Prefix "$2a$": bug = 0, safety = 0x10000 - * Prefix "$2b$": bug = 0, safety = 0 - * Prefix "$2x$": bug = 1, safety = 0 - * Prefix "$2y$": bug = 0, safety = 0 - */ - bug = (unsigned int)flags & 1; - safety = ((BF_word)flags & 2) << 15; - - sign = diff = 0; - - for (i = 0; i < BF_N + 2; i++) { - tmp[0] = tmp[1] = 0; - for (j = 0; j < 4; j++) { - tmp[0] <<= 8; - tmp[0] |= (unsigned char)*ptr; /* correct */ - tmp[1] <<= 8; - tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */ -/* - * Sign extension in the first char has no effect - nothing to overwrite yet, - * and those extra 24 bits will be fully shifted out of the 32-bit word. For - * chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign - * extension in tmp[1] occurs. Once this flag is set, it remains set. - */ - if (j) - sign |= tmp[1] & 0x80; - if (!*ptr) - ptr = key; - else - ptr++; - } - diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */ - - expanded[i] = tmp[bug]; - initial[i] = BF_init_state.P[i] ^ tmp[bug]; - } - -/* - * At this point, "diff" is zero iff the correct and buggy algorithms produced - * exactly the same result. If so and if "sign" is non-zero, which indicates - * that there was a non-benign sign extension, this means that we have a - * collision between the correctly computed hash for this password and a set of - * passwords that could be supplied to the buggy algorithm. Our safety measure - * is meant to protect from such many-buggy to one-correct collisions, by - * deviating from the correct algorithm in such cases. Let's check for this. - */ - diff |= diff >> 16; /* still zero iff exact match */ - diff &= 0xffff; /* ditto */ - diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */ - sign <<= 9; /* move the non-benign sign extension flag to bit 16 */ - sign &= ~diff & safety; /* action needed? */ - -/* - * If we have determined that we need to deviate from the correct algorithm, - * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but - * let's stick to it now. It came out of the approach we used above, and it's - * not any worse than any other choice we could make.) - * - * It is crucial that we don't do the same to the expanded key used in the main - * Eksblowfish loop. By doing it to only one of these two, we deviate from a - * state that could be directly specified by a password to the buggy algorithm - * (and to the fully correct one as well, but that's a side-effect). - */ - initial[0] ^= sign; -} - -static const unsigned char flags_by_subtype[26] = - {2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0}; - -static char *BF_crypt(const char *key, const char *setting, - char *output, int size, - BF_word min) -{ -#if BF_ASM - extern void _BF_body_r(BF_ctx *ctx); -#endif - struct { - BF_ctx ctx; - BF_key expanded_key; - union { - BF_word salt[4]; - BF_word output[6]; - } binary; - } data; - BF_word L, R; - BF_word tmp1, tmp2, tmp3, tmp4; - BF_word *ptr; - BF_word count; - int i; - - if (size < 7 + 22 + 31 + 1) { - __set_errno(ERANGE); - return NULL; - } - - if (setting[0] != '$' || - setting[1] != '2' || - setting[2] < 'a' || setting[2] > 'z' || - !flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] || - setting[3] != '$' || - setting[4] < '0' || setting[4] > '3' || - setting[5] < '0' || setting[5] > '9' || - (setting[4] == '3' && setting[5] > '1') || - setting[6] != '$') { - __set_errno(EINVAL); - return NULL; - } - - count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0')); - if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) { - __set_errno(EINVAL); - return NULL; - } - BF_swap(data.binary.salt, 4); - - BF_set_key(key, data.expanded_key, data.ctx.P, - flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']); - - memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S)); - - L = R = 0; - for (i = 0; i < BF_N + 2; i += 2) { - L ^= data.binary.salt[i & 2]; - R ^= data.binary.salt[(i & 2) + 1]; - BF_ENCRYPT; - data.ctx.P[i] = L; - data.ctx.P[i + 1] = R; - } - - ptr = data.ctx.S[0]; - do { - ptr += 4; - L ^= data.binary.salt[(BF_N + 2) & 3]; - R ^= data.binary.salt[(BF_N + 3) & 3]; - BF_ENCRYPT; - *(ptr - 4) = L; - *(ptr - 3) = R; - - L ^= data.binary.salt[(BF_N + 4) & 3]; - R ^= data.binary.salt[(BF_N + 5) & 3]; - BF_ENCRYPT; - *(ptr - 2) = L; - *(ptr - 1) = R; - } while (ptr < &data.ctx.S[3][0xFF]); - - do { - int done; - - for (i = 0; i < BF_N + 2; i += 2) { - data.ctx.P[i] ^= data.expanded_key[i]; - data.ctx.P[i + 1] ^= data.expanded_key[i + 1]; - } - - done = 0; - do { - BF_body(); - if (done) - break; - done = 1; - - tmp1 = data.binary.salt[0]; - tmp2 = data.binary.salt[1]; - tmp3 = data.binary.salt[2]; - tmp4 = data.binary.salt[3]; - for (i = 0; i < BF_N; i += 4) { - data.ctx.P[i] ^= tmp1; - data.ctx.P[i + 1] ^= tmp2; - data.ctx.P[i + 2] ^= tmp3; - data.ctx.P[i + 3] ^= tmp4; - } - data.ctx.P[16] ^= tmp1; - data.ctx.P[17] ^= tmp2; - } while (1); - } while (--count); - - for (i = 0; i < 6; i += 2) { - L = BF_magic_w[i]; - R = BF_magic_w[i + 1]; - - count = 64; - do { - BF_ENCRYPT; - } while (--count); - - data.binary.output[i] = L; - data.binary.output[i + 1] = R; - } - - memcpy(output, setting, 7 + 22 - 1); - output[7 + 22 - 1] = BF_itoa64[(int) - BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30]; - -/* This has to be bug-compatible with the original implementation, so - * only encode 23 of the 24 bytes. :-) */ - BF_swap(data.binary.output, 6); - BF_encode(&output[7 + 22], data.binary.output, 23); - output[7 + 22 + 31] = '\0'; - - return output; -} - -int _crypt_output_magic(const char *setting, char *output, int size) -{ - if (size < 3) - return -1; - - output[0] = '*'; - output[1] = '0'; - output[2] = '\0'; - - if (setting[0] == '*' && setting[1] == '0') - output[1] = '1'; - - return 0; -} - -/* - * Please preserve the runtime self-test. It serves two purposes at once: - * - * 1. We really can't afford the risk of producing incompatible hashes e.g. - * when there's something like gcc bug 26587 again, whereas an application or - * library integrating this code might not also integrate our external tests or - * it might not run them after every build. Even if it does, the miscompile - * might only occur on the production build, but not on a testing build (such - * as because of different optimization settings). It is painful to recover - * from incorrectly-computed hashes - merely fixing whatever broke is not - * enough. Thus, a proactive measure like this self-test is needed. - * - * 2. We don't want to leave sensitive data from our actual password hash - * computation on the stack or in registers. Previous revisions of the code - * would do explicit cleanups, but simply running the self-test after hash - * computation is more reliable. - * - * The performance cost of this quick self-test is around 0.6% at the "$2a$08" - * setting. - */ -char *_crypt_blowfish_rn(const char *key, const char *setting, - char *output, int size) -{ - const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8"; - const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu"; - static const char * const test_hashes[2] = - {"i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55", /* 'a', 'b', 'y' */ - "VUrPmXD6q/nVSSp7pNDhCR9071IfIRe\0\x55"}; /* 'x' */ - const char *test_hash = test_hashes[0]; - char *retval; - const char *p; - int save_errno, ok; - struct { - char s[7 + 22 + 1]; - char o[7 + 22 + 31 + 1 + 1 + 1]; - } buf; - -/* Hash the supplied password */ - _crypt_output_magic(setting, output, size); - retval = BF_crypt(key, setting, output, size, 16); - save_errno = errno; - -/* - * Do a quick self-test. It is important that we make both calls to BF_crypt() - * from the same scope such that they likely use the same stack locations, - * which makes the second call overwrite the first call's sensitive data on the - * stack and makes it more likely that any alignment related issues would be - * detected by the self-test. - */ - memcpy(buf.s, test_setting, sizeof(buf.s)); - if (retval) { - unsigned int flags = flags_by_subtype[ - (unsigned int)(unsigned char)setting[2] - 'a']; - test_hash = test_hashes[flags & 1]; - buf.s[2] = setting[2]; - } - memset(buf.o, 0x55, sizeof(buf.o)); - buf.o[sizeof(buf.o) - 1] = 0; - p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1); - - ok = (p == buf.o && - !memcmp(p, buf.s, 7 + 22) && - !memcmp(p + (7 + 22), test_hash, 31 + 1 + 1 + 1)); - - { - const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"; - BF_key ae, ai, ye, yi; - BF_set_key(k, ae, ai, 2); /* $2a$ */ - BF_set_key(k, ye, yi, 4); /* $2y$ */ - ai[0] ^= 0x10000; /* undo the safety (for comparison) */ - ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 && - !memcmp(ae, ye, sizeof(ae)) && - !memcmp(ai, yi, sizeof(ai)); - } - - __set_errno(save_errno); - if (ok) - return retval; - -/* Should not happen */ - _crypt_output_magic(setting, output, size); - __set_errno(EINVAL); /* pretend we don't support this hash type */ - return NULL; -} - -char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, - const char *input, int size, char *output, int output_size) -{ - if (size < 16 || output_size < 7 + 22 + 1 || - (count && (count < 4 || count > 31)) || - prefix[0] != '$' || prefix[1] != '2' || - (prefix[2] != 'a' && prefix[2] != 'b' && prefix[2] != 'y')) { - if (output_size > 0) output[0] = '\0'; - __set_errno((output_size < 7 + 22 + 1) ? ERANGE : EINVAL); - return NULL; - } - - if (!count) count = 5; - - output[0] = '$'; - output[1] = '2'; - output[2] = prefix[2]; - output[3] = '$'; - output[4] = '0' + count / 10; - output[5] = '0' + count % 10; - output[6] = '$'; - - BF_encode(&output[7], (const BF_word *)input, 16); - output[7 + 22] = '\0'; - - return output; -} +/* + * The crypt_blowfish homepage is: + * + * http://www.openwall.com/crypt/ + * + * This code comes from John the Ripper password cracker, with reentrant + * and crypt(3) interfaces added, but optimizations specific to password + * cracking removed. + * + * Written by Solar Designer in 1998-2014. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 1998-2014 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * It is my intent that you should be able to use this on your system, + * as part of a software package, or anywhere else to improve security, + * ensure compatibility, or for any other purpose. I would appreciate + * it if you give credit where it is due and keep your modifications in + * the public domain as well, but I don't require that in order to let + * you place this code and any modifications you make under a license + * of your choice. + * + * This implementation is fully compatible with OpenBSD's bcrypt.c for prefix + * "$2b$", originally by Niels Provos , and it uses + * some of his ideas. The password hashing algorithm was designed by David + * Mazieres . For information on the level of + * compatibility for bcrypt hash prefixes other than "$2b$", please refer to + * the comments in BF_set_key() below and to the included crypt(3) man page. + * + * There's a paper on the algorithm that explains its design decisions: + * + * http://www.usenix.org/events/usenix99/provos.html + * + * Some of the tricks in BF_ROUND might be inspired by Eric Young's + * Blowfish library (I can't be sure if I would think of something if I + * hadn't seen his code). + */ + +#include + +#include +#ifndef __set_errno +#define __set_errno(val) errno = (val) +#endif + +/* Just to make sure the prototypes match the actual definitions */ +#ifdef _WIN32 || _WIN64 +#include "../include/bcrypt/crypt_blowfish.h" +#else +#include "crypt_blowfish.h" +#endif + +#ifdef __i386__ +#define BF_ASM 1 +#define BF_SCALE 1 +#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__) +#define BF_ASM 0 +#define BF_SCALE 1 +#else +#define BF_ASM 0 +#define BF_SCALE 0 +#endif + +typedef unsigned int BF_word; +typedef signed int BF_word_signed; + +/* Number of Blowfish rounds, this is also hardcoded into a few places */ +#define BF_N 16 + +typedef BF_word BF_key[BF_N + 2]; + +typedef struct { + BF_word S[4][0x100]; + BF_key P; +} BF_ctx; + +/* + * Magic IV for 64 Blowfish encryptions that we do at the end. + * The string is "OrpheanBeholderScryDoubt" on big-endian. + */ +static BF_word BF_magic_w[6] = { + 0x4F727068, 0x65616E42, 0x65686F6C, + 0x64657253, 0x63727944, 0x6F756274 +}; + +/* + * P-box and S-box tables initialized with digits of Pi. + */ +static BF_ctx BF_init_state = { + { + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a + }, { + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 + }, { + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 + }, { + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 + } + }, { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + } +}; + +static unsigned char BF_itoa64[64 + 1] = + "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +static unsigned char BF_atoi64[0x60] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64, + 64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 64, 64, 64, 64, 64, + 64, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64 +}; + +#define BF_safe_atoi64(dst, src) \ +{ \ + tmp = (unsigned char)(src); \ + if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ + tmp = BF_atoi64[tmp]; \ + if (tmp > 63) return -1; \ + (dst) = tmp; \ +} + +static int BF_decode(BF_word *dst, const char *src, int size) +{ + unsigned char *dptr = (unsigned char *)dst; + unsigned char *end = dptr + size; + const unsigned char *sptr = (const unsigned char *)src; + unsigned int tmp, c1, c2, c3, c4; + + do { + BF_safe_atoi64(c1, *sptr++); + BF_safe_atoi64(c2, *sptr++); + *dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4); + if (dptr >= end) break; + + BF_safe_atoi64(c3, *sptr++); + *dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2); + if (dptr >= end) break; + + BF_safe_atoi64(c4, *sptr++); + *dptr++ = ((c3 & 0x03) << 6) | c4; + } while (dptr < end); + + return 0; +} + +static void BF_encode(char *dst, const BF_word *src, int size) +{ + const unsigned char *sptr = (const unsigned char *)src; + const unsigned char *end = sptr + size; + unsigned char *dptr = (unsigned char *)dst; + unsigned int c1, c2; + + do { + c1 = *sptr++; + *dptr++ = BF_itoa64[c1 >> 2]; + c1 = (c1 & 0x03) << 4; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 4; + *dptr++ = BF_itoa64[c1]; + c1 = (c2 & 0x0f) << 2; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 6; + *dptr++ = BF_itoa64[c1]; + *dptr++ = BF_itoa64[c2 & 0x3f]; + } while (sptr < end); +} + +static void BF_swap(BF_word *x, int count) +{ + static int endianness_check = 1; + char *is_little_endian = (char *)&endianness_check; + BF_word tmp; + + if (*is_little_endian) + do { + tmp = *x; + tmp = (tmp << 16) | (tmp >> 16); + *x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF); + } while (--count); +} + +#if BF_SCALE +/* Architectures which can shift addresses left by 2 bits with no extra cost */ +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp2 = L >> 8; \ + tmp2 &= 0xFF; \ + tmp3 = L >> 16; \ + tmp3 &= 0xFF; \ + tmp4 = L >> 24; \ + tmp1 = data.ctx.S[3][tmp1]; \ + tmp2 = data.ctx.S[2][tmp2]; \ + tmp3 = data.ctx.S[1][tmp3]; \ + tmp3 += data.ctx.S[0][tmp4]; \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#else +/* Architectures with no complicated addressing modes supported */ +#define BF_INDEX(S, i) \ + (*((BF_word *)(((unsigned char *)S) + (i)))) +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp1 <<= 2; \ + tmp2 = L >> 6; \ + tmp2 &= 0x3FC; \ + tmp3 = L >> 14; \ + tmp3 &= 0x3FC; \ + tmp4 = L >> 22; \ + tmp4 &= 0x3FC; \ + tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \ + tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \ + tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \ + tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#endif + +/* + * Encrypt one block, BF_N is hardcoded here. + */ +#define BF_ENCRYPT \ + L ^= data.ctx.P[0]; \ + BF_ROUND(L, R, 0); \ + BF_ROUND(R, L, 1); \ + BF_ROUND(L, R, 2); \ + BF_ROUND(R, L, 3); \ + BF_ROUND(L, R, 4); \ + BF_ROUND(R, L, 5); \ + BF_ROUND(L, R, 6); \ + BF_ROUND(R, L, 7); \ + BF_ROUND(L, R, 8); \ + BF_ROUND(R, L, 9); \ + BF_ROUND(L, R, 10); \ + BF_ROUND(R, L, 11); \ + BF_ROUND(L, R, 12); \ + BF_ROUND(R, L, 13); \ + BF_ROUND(L, R, 14); \ + BF_ROUND(R, L, 15); \ + tmp4 = R; \ + R = L; \ + L = tmp4 ^ data.ctx.P[BF_N + 1]; + +#if BF_ASM +#define BF_body() \ + _BF_body_r(&data.ctx); +#else +#define BF_body() \ + L = R = 0; \ + ptr = data.ctx.P; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.P[BF_N + 2]); \ +\ + ptr = data.ctx.S[0]; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.S[3][0xFF]); +#endif + +static void BF_set_key(const char *key, BF_key expanded, BF_key initial, + unsigned char flags) +{ + const char *ptr = key; + unsigned int bug, i, j; + BF_word safety, sign, diff, tmp[2]; + +/* + * There was a sign extension bug in older revisions of this function. While + * we would have liked to simply fix the bug and move on, we have to provide + * a backwards compatibility feature (essentially the bug) for some systems and + * a safety measure for some others. The latter is needed because for certain + * multiple inputs to the buggy algorithm there exist easily found inputs to + * the correct algorithm that produce the same hash. Thus, we optionally + * deviate from the correct algorithm just enough to avoid such collisions. + * While the bug itself affected the majority of passwords containing + * characters with the 8th bit set (although only a percentage of those in a + * collision-producing way), the anti-collision safety measure affects + * only a subset of passwords containing the '\xff' character (not even all of + * those passwords, just some of them). This character is not found in valid + * UTF-8 sequences and is rarely used in popular 8-bit character encodings. + * Thus, the safety measure is unlikely to cause much annoyance, and is a + * reasonable tradeoff to use when authenticating against existing hashes that + * are not reliably known to have been computed with the correct algorithm. + * + * We use an approach that tries to minimize side-channel leaks of password + * information - that is, we mostly use fixed-cost bitwise operations instead + * of branches or table lookups. (One conditional branch based on password + * length remains. It is not part of the bug aftermath, though, and is + * difficult and possibly unreasonable to avoid given the use of C strings by + * the caller, which results in similar timing leaks anyway.) + * + * For actual implementation, we set an array index in the variable "bug" + * (0 means no bug, 1 means sign extension bug emulation) and a flag in the + * variable "safety" (bit 16 is set when the safety measure is requested). + * Valid combinations of settings are: + * + * Prefix "$2a$": bug = 0, safety = 0x10000 + * Prefix "$2b$": bug = 0, safety = 0 + * Prefix "$2x$": bug = 1, safety = 0 + * Prefix "$2y$": bug = 0, safety = 0 + */ + bug = (unsigned int)flags & 1; + safety = ((BF_word)flags & 2) << 15; + + sign = diff = 0; + + for (i = 0; i < BF_N + 2; i++) { + tmp[0] = tmp[1] = 0; + for (j = 0; j < 4; j++) { + tmp[0] <<= 8; + tmp[0] |= (unsigned char)*ptr; /* correct */ + tmp[1] <<= 8; + tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */ +/* + * Sign extension in the first char has no effect - nothing to overwrite yet, + * and those extra 24 bits will be fully shifted out of the 32-bit word. For + * chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign + * extension in tmp[1] occurs. Once this flag is set, it remains set. + */ + if (j) + sign |= tmp[1] & 0x80; + if (!*ptr) + ptr = key; + else + ptr++; + } + diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */ + + expanded[i] = tmp[bug]; + initial[i] = BF_init_state.P[i] ^ tmp[bug]; + } + +/* + * At this point, "diff" is zero iff the correct and buggy algorithms produced + * exactly the same result. If so and if "sign" is non-zero, which indicates + * that there was a non-benign sign extension, this means that we have a + * collision between the correctly computed hash for this password and a set of + * passwords that could be supplied to the buggy algorithm. Our safety measure + * is meant to protect from such many-buggy to one-correct collisions, by + * deviating from the correct algorithm in such cases. Let's check for this. + */ + diff |= diff >> 16; /* still zero iff exact match */ + diff &= 0xffff; /* ditto */ + diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */ + sign <<= 9; /* move the non-benign sign extension flag to bit 16 */ + sign &= ~diff & safety; /* action needed? */ + +/* + * If we have determined that we need to deviate from the correct algorithm, + * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but + * let's stick to it now. It came out of the approach we used above, and it's + * not any worse than any other choice we could make.) + * + * It is crucial that we don't do the same to the expanded key used in the main + * Eksblowfish loop. By doing it to only one of these two, we deviate from a + * state that could be directly specified by a password to the buggy algorithm + * (and to the fully correct one as well, but that's a side-effect). + */ + initial[0] ^= sign; +} + +static const unsigned char flags_by_subtype[26] = + {2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0}; + +static char *BF_crypt(const char *key, const char *setting, + char *output, int size, + BF_word min) +{ +#if BF_ASM + extern void _BF_body_r(BF_ctx *ctx); +#endif + struct { + BF_ctx ctx; + BF_key expanded_key; + union { + BF_word salt[4]; + BF_word output[6]; + } binary; + } data; + BF_word L, R; + BF_word tmp1, tmp2, tmp3, tmp4; + BF_word *ptr; + BF_word count; + int i; + + if (size < 7 + 22 + 31 + 1) { + __set_errno(ERANGE); + return NULL; + } + + if (setting[0] != '$' || + setting[1] != '2' || + setting[2] < 'a' || setting[2] > 'z' || + !flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] || + setting[3] != '$' || + setting[4] < '0' || setting[4] > '3' || + setting[5] < '0' || setting[5] > '9' || + (setting[4] == '3' && setting[5] > '1') || + setting[6] != '$') { + __set_errno(EINVAL); + return NULL; + } + + count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0')); + if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) { + __set_errno(EINVAL); + return NULL; + } + BF_swap(data.binary.salt, 4); + + BF_set_key(key, data.expanded_key, data.ctx.P, + flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']); + + memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S)); + + L = R = 0; + for (i = 0; i < BF_N + 2; i += 2) { + L ^= data.binary.salt[i & 2]; + R ^= data.binary.salt[(i & 2) + 1]; + BF_ENCRYPT; + data.ctx.P[i] = L; + data.ctx.P[i + 1] = R; + } + + ptr = data.ctx.S[0]; + do { + ptr += 4; + L ^= data.binary.salt[(BF_N + 2) & 3]; + R ^= data.binary.salt[(BF_N + 3) & 3]; + BF_ENCRYPT; + *(ptr - 4) = L; + *(ptr - 3) = R; + + L ^= data.binary.salt[(BF_N + 4) & 3]; + R ^= data.binary.salt[(BF_N + 5) & 3]; + BF_ENCRYPT; + *(ptr - 2) = L; + *(ptr - 1) = R; + } while (ptr < &data.ctx.S[3][0xFF]); + + do { + int done; + + for (i = 0; i < BF_N + 2; i += 2) { + data.ctx.P[i] ^= data.expanded_key[i]; + data.ctx.P[i + 1] ^= data.expanded_key[i + 1]; + } + + done = 0; + do { + BF_body(); + if (done) + break; + done = 1; + + tmp1 = data.binary.salt[0]; + tmp2 = data.binary.salt[1]; + tmp3 = data.binary.salt[2]; + tmp4 = data.binary.salt[3]; + for (i = 0; i < BF_N; i += 4) { + data.ctx.P[i] ^= tmp1; + data.ctx.P[i + 1] ^= tmp2; + data.ctx.P[i + 2] ^= tmp3; + data.ctx.P[i + 3] ^= tmp4; + } + data.ctx.P[16] ^= tmp1; + data.ctx.P[17] ^= tmp2; + } while (1); + } while (--count); + + for (i = 0; i < 6; i += 2) { + L = BF_magic_w[i]; + R = BF_magic_w[i + 1]; + + count = 64; + do { + BF_ENCRYPT; + } while (--count); + + data.binary.output[i] = L; + data.binary.output[i + 1] = R; + } + + memcpy(output, setting, 7 + 22 - 1); + output[7 + 22 - 1] = BF_itoa64[(int) + BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30]; + +/* This has to be bug-compatible with the original implementation, so + * only encode 23 of the 24 bytes. :-) */ + BF_swap(data.binary.output, 6); + BF_encode(&output[7 + 22], data.binary.output, 23); + output[7 + 22 + 31] = '\0'; + + return output; +} + +int _crypt_output_magic(const char *setting, char *output, int size) +{ + if (size < 3) + return -1; + + output[0] = '*'; + output[1] = '0'; + output[2] = '\0'; + + if (setting[0] == '*' && setting[1] == '0') + output[1] = '1'; + + return 0; +} + +/* + * Please preserve the runtime self-test. It serves two purposes at once: + * + * 1. We really can't afford the risk of producing incompatible hashes e.g. + * when there's something like gcc bug 26587 again, whereas an application or + * library integrating this code might not also integrate our external tests or + * it might not run them after every build. Even if it does, the miscompile + * might only occur on the production build, but not on a testing build (such + * as because of different optimization settings). It is painful to recover + * from incorrectly-computed hashes - merely fixing whatever broke is not + * enough. Thus, a proactive measure like this self-test is needed. + * + * 2. We don't want to leave sensitive data from our actual password hash + * computation on the stack or in registers. Previous revisions of the code + * would do explicit cleanups, but simply running the self-test after hash + * computation is more reliable. + * + * The performance cost of this quick self-test is around 0.6% at the "$2a$08" + * setting. + */ +char *_crypt_blowfish_rn(const char *key, const char *setting, + char *output, int size) +{ + const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8"; + const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu"; + static const char * const test_hashes[2] = + {"i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55", /* 'a', 'b', 'y' */ + "VUrPmXD6q/nVSSp7pNDhCR9071IfIRe\0\x55"}; /* 'x' */ + const char *test_hash = test_hashes[0]; + char *retval; + const char *p; + int save_errno, ok; + struct { + char s[7 + 22 + 1]; + char o[7 + 22 + 31 + 1 + 1 + 1]; + } buf; + +/* Hash the supplied password */ + _crypt_output_magic(setting, output, size); + retval = BF_crypt(key, setting, output, size, 16); + save_errno = errno; + +/* + * Do a quick self-test. It is important that we make both calls to BF_crypt() + * from the same scope such that they likely use the same stack locations, + * which makes the second call overwrite the first call's sensitive data on the + * stack and makes it more likely that any alignment related issues would be + * detected by the self-test. + */ + memcpy(buf.s, test_setting, sizeof(buf.s)); + if (retval) { + unsigned int flags = flags_by_subtype[ + (unsigned int)(unsigned char)setting[2] - 'a']; + test_hash = test_hashes[flags & 1]; + buf.s[2] = setting[2]; + } + memset(buf.o, 0x55, sizeof(buf.o)); + buf.o[sizeof(buf.o) - 1] = 0; + p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1); + + ok = (p == buf.o && + !memcmp(p, buf.s, 7 + 22) && + !memcmp(p + (7 + 22), test_hash, 31 + 1 + 1 + 1)); + + { + const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"; + BF_key ae, ai, ye, yi; + BF_set_key(k, ae, ai, 2); /* $2a$ */ + BF_set_key(k, ye, yi, 4); /* $2y$ */ + ai[0] ^= 0x10000; /* undo the safety (for comparison) */ + ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 && + !memcmp(ae, ye, sizeof(ae)) && + !memcmp(ai, yi, sizeof(ai)); + } + + __set_errno(save_errno); + if (ok) + return retval; + +/* Should not happen */ + _crypt_output_magic(setting, output, size); + __set_errno(EINVAL); /* pretend we don't support this hash type */ + return NULL; +} + +char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + if (size < 16 || output_size < 7 + 22 + 1 || + (count && (count < 4 || count > 31)) || + prefix[0] != '$' || prefix[1] != '2' || + (prefix[2] != 'a' && prefix[2] != 'b' && prefix[2] != 'y')) { + if (output_size > 0) output[0] = '\0'; + __set_errno((output_size < 7 + 22 + 1) ? ERANGE : EINVAL); + return NULL; + } + + if (!count) count = 5; + + output[0] = '$'; + output[1] = '2'; + output[2] = prefix[2]; + output[3] = '$'; + output[4] = '0' + count / 10; + output[5] = '0' + count % 10; + output[6] = '$'; + + BF_encode(&output[7], (const BF_word *)input, 16); + output[7 + 22] = '\0'; + + return output; +} diff --git a/src/libbcrypt/src/crypt_gensalt.c b/dep/libbcrypt/src/crypt_gensalt.c similarity index 96% rename from src/libbcrypt/src/crypt_gensalt.c rename to dep/libbcrypt/src/crypt_gensalt.c index 1f0d73da0..a1d719ec4 100644 --- a/src/libbcrypt/src/crypt_gensalt.c +++ b/dep/libbcrypt/src/crypt_gensalt.c @@ -1,128 +1,128 @@ -/* - * Written by Solar Designer in 2000-2011. - * No copyright is claimed, and the software is hereby placed in the public - * domain. In case this attempt to disclaim copyright and place the software - * in the public domain is deemed null and void, then the software is - * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the - * general public under the following terms: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted. - * - * There's ABSOLUTELY NO WARRANTY, express or implied. - * - * See crypt_blowfish.c for more information. - * - * This file contains salt generation functions for the traditional and - * other common crypt(3) algorithms, except for bcrypt which is defined - * entirely in crypt_blowfish.c. - */ - -#include - -#include -#ifndef __set_errno -#define __set_errno(val) errno = (val) -#endif - -/* Just to make sure the prototypes match the actual definitions */ -#ifdef _WIN32 -#include "../include/bcrypt/crypt_gensalt.h" -#else -#include "crypt_gensalt.h" -#endif - -unsigned char _crypt_itoa64[64 + 1] = - "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - -char *_crypt_gensalt_traditional_rn(const char *prefix, unsigned long count, - const char *input, int size, char *output, int output_size) -{ - (void) prefix; - - if (size < 2 || output_size < 2 + 1 || (count && count != 25)) { - if (output_size > 0) output[0] = '\0'; - __set_errno((output_size < 2 + 1) ? ERANGE : EINVAL); - return NULL; - } - - output[0] = _crypt_itoa64[(unsigned int)input[0] & 0x3f]; - output[1] = _crypt_itoa64[(unsigned int)input[1] & 0x3f]; - output[2] = '\0'; - - return output; -} - -char *_crypt_gensalt_extended_rn(const char *prefix, unsigned long count, - const char *input, int size, char *output, int output_size) -{ - unsigned long value; - - (void) prefix; - -/* Even iteration counts make it easier to detect weak DES keys from a look - * at the hash, so they should be avoided */ - if (size < 3 || output_size < 1 + 4 + 4 + 1 || - (count && (count > 0xffffff || !(count & 1)))) { - if (output_size > 0) output[0] = '\0'; - __set_errno((output_size < 1 + 4 + 4 + 1) ? ERANGE : EINVAL); - return NULL; - } - - if (!count) count = 725; - - output[0] = '_'; - output[1] = _crypt_itoa64[count & 0x3f]; - output[2] = _crypt_itoa64[(count >> 6) & 0x3f]; - output[3] = _crypt_itoa64[(count >> 12) & 0x3f]; - output[4] = _crypt_itoa64[(count >> 18) & 0x3f]; - value = (unsigned long)(unsigned char)input[0] | - ((unsigned long)(unsigned char)input[1] << 8) | - ((unsigned long)(unsigned char)input[2] << 16); - output[5] = _crypt_itoa64[value & 0x3f]; - output[6] = _crypt_itoa64[(value >> 6) & 0x3f]; - output[7] = _crypt_itoa64[(value >> 12) & 0x3f]; - output[8] = _crypt_itoa64[(value >> 18) & 0x3f]; - output[9] = '\0'; - - return output; -} - -char *_crypt_gensalt_md5_rn(const char *prefix, unsigned long count, - const char *input, int size, char *output, int output_size) -{ - unsigned long value; - - (void) prefix; - - if (size < 3 || output_size < 3 + 4 + 1 || (count && count != 1000)) { - if (output_size > 0) output[0] = '\0'; - __set_errno((output_size < 3 + 4 + 1) ? ERANGE : EINVAL); - return NULL; - } - - output[0] = '$'; - output[1] = '1'; - output[2] = '$'; - value = (unsigned long)(unsigned char)input[0] | - ((unsigned long)(unsigned char)input[1] << 8) | - ((unsigned long)(unsigned char)input[2] << 16); - output[3] = _crypt_itoa64[value & 0x3f]; - output[4] = _crypt_itoa64[(value >> 6) & 0x3f]; - output[5] = _crypt_itoa64[(value >> 12) & 0x3f]; - output[6] = _crypt_itoa64[(value >> 18) & 0x3f]; - output[7] = '\0'; - - if (size >= 6 && output_size >= 3 + 4 + 4 + 1) { - value = (unsigned long)(unsigned char)input[3] | - ((unsigned long)(unsigned char)input[4] << 8) | - ((unsigned long)(unsigned char)input[5] << 16); - output[7] = _crypt_itoa64[value & 0x3f]; - output[8] = _crypt_itoa64[(value >> 6) & 0x3f]; - output[9] = _crypt_itoa64[(value >> 12) & 0x3f]; - output[10] = _crypt_itoa64[(value >> 18) & 0x3f]; - output[11] = '\0'; - } - - return output; -} +/* + * Written by Solar Designer in 2000-2011. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * See crypt_blowfish.c for more information. + * + * This file contains salt generation functions for the traditional and + * other common crypt(3) algorithms, except for bcrypt which is defined + * entirely in crypt_blowfish.c. + */ + +#include + +#include +#ifndef __set_errno +#define __set_errno(val) errno = (val) +#endif + +/* Just to make sure the prototypes match the actual definitions */ +#ifdef _WIN32 +#include "../include/bcrypt/crypt_gensalt.h" +#else +#include "crypt_gensalt.h" +#endif + +unsigned char _crypt_itoa64[64 + 1] = + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +char *_crypt_gensalt_traditional_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + (void) prefix; + + if (size < 2 || output_size < 2 + 1 || (count && count != 25)) { + if (output_size > 0) output[0] = '\0'; + __set_errno((output_size < 2 + 1) ? ERANGE : EINVAL); + return NULL; + } + + output[0] = _crypt_itoa64[(unsigned int)input[0] & 0x3f]; + output[1] = _crypt_itoa64[(unsigned int)input[1] & 0x3f]; + output[2] = '\0'; + + return output; +} + +char *_crypt_gensalt_extended_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + unsigned long value; + + (void) prefix; + +/* Even iteration counts make it easier to detect weak DES keys from a look + * at the hash, so they should be avoided */ + if (size < 3 || output_size < 1 + 4 + 4 + 1 || + (count && (count > 0xffffff || !(count & 1)))) { + if (output_size > 0) output[0] = '\0'; + __set_errno((output_size < 1 + 4 + 4 + 1) ? ERANGE : EINVAL); + return NULL; + } + + if (!count) count = 725; + + output[0] = '_'; + output[1] = _crypt_itoa64[count & 0x3f]; + output[2] = _crypt_itoa64[(count >> 6) & 0x3f]; + output[3] = _crypt_itoa64[(count >> 12) & 0x3f]; + output[4] = _crypt_itoa64[(count >> 18) & 0x3f]; + value = (unsigned long)(unsigned char)input[0] | + ((unsigned long)(unsigned char)input[1] << 8) | + ((unsigned long)(unsigned char)input[2] << 16); + output[5] = _crypt_itoa64[value & 0x3f]; + output[6] = _crypt_itoa64[(value >> 6) & 0x3f]; + output[7] = _crypt_itoa64[(value >> 12) & 0x3f]; + output[8] = _crypt_itoa64[(value >> 18) & 0x3f]; + output[9] = '\0'; + + return output; +} + +char *_crypt_gensalt_md5_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + unsigned long value; + + (void) prefix; + + if (size < 3 || output_size < 3 + 4 + 1 || (count && count != 1000)) { + if (output_size > 0) output[0] = '\0'; + __set_errno((output_size < 3 + 4 + 1) ? ERANGE : EINVAL); + return NULL; + } + + output[0] = '$'; + output[1] = '1'; + output[2] = '$'; + value = (unsigned long)(unsigned char)input[0] | + ((unsigned long)(unsigned char)input[1] << 8) | + ((unsigned long)(unsigned char)input[2] << 16); + output[3] = _crypt_itoa64[value & 0x3f]; + output[4] = _crypt_itoa64[(value >> 6) & 0x3f]; + output[5] = _crypt_itoa64[(value >> 12) & 0x3f]; + output[6] = _crypt_itoa64[(value >> 18) & 0x3f]; + output[7] = '\0'; + + if (size >= 6 && output_size >= 3 + 4 + 4 + 1) { + value = (unsigned long)(unsigned char)input[3] | + ((unsigned long)(unsigned char)input[4] << 8) | + ((unsigned long)(unsigned char)input[5] << 16); + output[7] = _crypt_itoa64[value & 0x3f]; + output[8] = _crypt_itoa64[(value >> 6) & 0x3f]; + output[9] = _crypt_itoa64[(value >> 12) & 0x3f]; + output[10] = _crypt_itoa64[(value >> 18) & 0x3f]; + output[11] = '\0'; + } + + return output; +} diff --git a/src/libbcrypt/src/main.cpp b/dep/libbcrypt/src/main.cpp similarity index 100% rename from src/libbcrypt/src/main.cpp rename to dep/libbcrypt/src/main.cpp diff --git a/src/libbcrypt/src/wrapper.c b/dep/libbcrypt/src/wrapper.c similarity index 96% rename from src/libbcrypt/src/wrapper.c rename to dep/libbcrypt/src/wrapper.c index 98ffeecfd..995e07ca5 100644 --- a/src/libbcrypt/src/wrapper.c +++ b/dep/libbcrypt/src/wrapper.c @@ -1,563 +1,563 @@ -/* - * Written by Solar Designer in 2000-2014. - * No copyright is claimed, and the software is hereby placed in the public - * domain. In case this attempt to disclaim copyright and place the software - * in the public domain is deemed null and void, then the software is - * Copyright (c) 2000-2014 Solar Designer and it is hereby released to the - * general public under the following terms: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted. - * - * There's ABSOLUTELY NO WARRANTY, express or implied. - * - * See crypt_blowfish.c for more information. - */ - -#include -#include - -#include -#ifndef __set_errno -#define __set_errno(val) errno = (val) -#endif - -#ifdef TEST -#include -#include -#include -#include -#include -#include -#ifdef TEST_THREADS -#include -#endif -#endif - -#define CRYPT_OUTPUT_SIZE (7 + 22 + 31 + 1) -#define CRYPT_GENSALT_OUTPUT_SIZE (7 + 22 + 1) - -#if defined(__GLIBC__) && defined(_LIBC) -#define __SKIP_GNU -#endif - -#ifdef _WIN32 | _WIN64 -#include "../include/bcrypt/ow-crypt.h" - -#include "../include/bcrypt/crypt_blowfish.h" -#include "../include/bcrypt/crypt_gensalt.h" -#else -#include "ow-crypt.h" - -#include "crypt_blowfish.h" -#include "crypt_gensalt.h" -#endif - -#if defined(__GLIBC__) && defined(_LIBC) -/* crypt.h from glibc-crypt-2.1 will define struct crypt_data for us */ -#include "crypt.h" -extern char *__md5_crypt_r(const char *key, const char *salt, - char *buffer, int buflen); -/* crypt-entry.c needs to be patched to define __des_crypt_r rather than - * __crypt_r, and not define crypt_r and crypt at all */ -extern char *__des_crypt_r(const char *key, const char *salt, - struct crypt_data *data); -extern struct crypt_data _ufc_foobar; -#endif - -static int _crypt_data_alloc(void **data, int *size, int need) -{ - void *updated; - - if (*data && *size >= need) return 0; - - updated = realloc(*data, need); - - if (!updated) { -#ifndef __GLIBC__ - /* realloc(3) on glibc sets errno, so we don't need to bother */ - __set_errno(ENOMEM); -#endif - return -1; - } - -#if defined(__GLIBC__) && defined(_LIBC) - if (need >= sizeof(struct crypt_data)) - ((struct crypt_data *)updated)->initialized = 0; -#endif - - *data = updated; - *size = need; - - return 0; -} - -static char *_crypt_retval_magic(char *retval, const char *setting, - char *output, int size) -{ - if (retval) - return retval; - - if (_crypt_output_magic(setting, output, size)) - return NULL; /* shouldn't happen */ - - return output; -} - -#if defined(__GLIBC__) && defined(_LIBC) -/* - * Applications may re-use the same instance of struct crypt_data without - * resetting the initialized field in order to let crypt_r() skip some of - * its initialization code. Thus, it is important that our multiple hashing - * algorithms either don't conflict with each other in their use of the - * data area or reset the initialized field themselves whenever required. - * Currently, the hashing algorithms simply have no conflicts: the first - * field of struct crypt_data is the 128-byte large DES key schedule which - * __des_crypt_r() calculates each time it is called while the two other - * hashing algorithms use less than 128 bytes of the data area. - */ - -char *__crypt_rn(__const char *key, __const char *setting, - void *data, int size) -{ - if (setting[0] == '$' && setting[1] == '2') - return _crypt_blowfish_rn(key, setting, (char *)data, size); - if (setting[0] == '$' && setting[1] == '1') - return __md5_crypt_r(key, setting, (char *)data, size); - if (setting[0] == '$' || setting[0] == '_') { - __set_errno(EINVAL); - return NULL; - } - if (size >= sizeof(struct crypt_data)) - return __des_crypt_r(key, setting, (struct crypt_data *)data); - __set_errno(ERANGE); - return NULL; -} - -char *__crypt_ra(__const char *key, __const char *setting, - void **data, int *size) -{ - if (setting[0] == '$' && setting[1] == '2') { - if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE)) - return NULL; - return _crypt_blowfish_rn(key, setting, (char *)*data, *size); - } - if (setting[0] == '$' && setting[1] == '1') { - if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE)) - return NULL; - return __md5_crypt_r(key, setting, (char *)*data, *size); - } - if (setting[0] == '$' || setting[0] == '_') { - __set_errno(EINVAL); - return NULL; - } - if (_crypt_data_alloc(data, size, sizeof(struct crypt_data))) - return NULL; - return __des_crypt_r(key, setting, (struct crypt_data *)*data); -} - -char *__crypt_r(__const char *key, __const char *setting, - struct crypt_data *data) -{ - return _crypt_retval_magic( - __crypt_rn(key, setting, data, sizeof(*data)), - setting, (char *)data, sizeof(*data)); -} - -char *__crypt(__const char *key, __const char *setting) -{ - return _crypt_retval_magic( - __crypt_rn(key, setting, &_ufc_foobar, sizeof(_ufc_foobar)), - setting, (char *)&_ufc_foobar, sizeof(_ufc_foobar)); -} -#else -char *crypt_rn(const char *key, const char *setting, void *data, int size) -{ - return _crypt_blowfish_rn(key, setting, (char *)data, size); -} - -char *crypt_ra(const char *key, const char *setting, - void **data, int *size) -{ - if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE)) - return NULL; - return _crypt_blowfish_rn(key, setting, (char *)*data, *size); -} - -char *crypt_r(const char *key, const char *setting, void *data) -{ - return _crypt_retval_magic( - crypt_rn(key, setting, data, CRYPT_OUTPUT_SIZE), - setting, (char *)data, CRYPT_OUTPUT_SIZE); -} - -char *crypt(const char *key, const char *setting) -{ - static char output[CRYPT_OUTPUT_SIZE]; - - return _crypt_retval_magic( - crypt_rn(key, setting, output, sizeof(output)), - setting, output, sizeof(output)); -} - -#define __crypt_gensalt_rn crypt_gensalt_rn -#define __crypt_gensalt_ra crypt_gensalt_ra -#define __crypt_gensalt crypt_gensalt -#endif - -char *__crypt_gensalt_rn(const char *prefix, unsigned long count, - const char *input, int size, char *output, int output_size) -{ - char *(*use)(const char *_prefix, unsigned long _count, - const char *_input, int _size, - char *_output, int _output_size); - - /* This may be supported on some platforms in the future */ - if (!input) { - __set_errno(EINVAL); - return NULL; - } - - if (!strncmp(prefix, "$2a$", 4) || !strncmp(prefix, "$2b$", 4) || - !strncmp(prefix, "$2y$", 4)) - use = _crypt_gensalt_blowfish_rn; - else - if (!strncmp(prefix, "$1$", 3)) - use = _crypt_gensalt_md5_rn; - else - if (prefix[0] == '_') - use = _crypt_gensalt_extended_rn; - else - if (!prefix[0] || - (prefix[0] && prefix[1] && - memchr(_crypt_itoa64, prefix[0], 64) && - memchr(_crypt_itoa64, prefix[1], 64))) - use = _crypt_gensalt_traditional_rn; - else { - __set_errno(EINVAL); - return NULL; - } - - return use(prefix, count, input, size, output, output_size); -} - -char *__crypt_gensalt_ra(const char *prefix, unsigned long count, - const char *input, int size) -{ - char output[CRYPT_GENSALT_OUTPUT_SIZE]; - char *retval; - - retval = __crypt_gensalt_rn(prefix, count, - input, size, output, sizeof(output)); - - if (retval) { -#ifdef _WIN32 | _WIN64 - retval = _strdup(retval); -#else - retval = strdup(retval); -#endif -#ifndef __GLIBC__ - /* strdup(3) on glibc sets errno, so we don't need to bother */ - if (!retval) - __set_errno(ENOMEM); -#endif - } - - return retval; -} - -char *__crypt_gensalt(const char *prefix, unsigned long count, - const char *input, int size) -{ - static char output[CRYPT_GENSALT_OUTPUT_SIZE]; - - return __crypt_gensalt_rn(prefix, count, - input, size, output, sizeof(output)); -} - -#if defined(__GLIBC__) && defined(_LIBC) -weak_alias(__crypt_rn, crypt_rn) -weak_alias(__crypt_ra, crypt_ra) -weak_alias(__crypt_r, crypt_r) -weak_alias(__crypt, crypt) -weak_alias(__crypt_gensalt_rn, crypt_gensalt_rn) -weak_alias(__crypt_gensalt_ra, crypt_gensalt_ra) -weak_alias(__crypt_gensalt, crypt_gensalt) -weak_alias(crypt, fcrypt) -#endif - -#ifdef TEST -static const char *tests[][3] = { - {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW", - "U*U"}, - {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK", - "U*U*"}, - {"$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a", - "U*U*U"}, - {"$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui", - "0123456789abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - "chars after 72 are ignored"}, - {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", - "\xa3"}, - {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", - "\xff\xff\xa3"}, - {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", - "\xff\xff\xa3"}, - {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.nqd1wy.pTMdcvrRWxyiGL2eMz.2a85.", - "\xff\xff\xa3"}, - {"$2b$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", - "\xff\xff\xa3"}, - {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq", - "\xa3"}, - {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq", - "\xa3"}, - {"$2b$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq", - "\xa3"}, - {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", - "1\xa3" "345"}, - {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", - "\xff\xa3" "345"}, - {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", - "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"}, - {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", - "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"}, - {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.ZC1JEJ8Z4gPfpe1JOr/oyPXTWl9EFd.", - "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"}, - {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e", - "\xff\xa3" "345"}, - {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e", - "\xff\xa3" "345"}, - {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS", - "\xa3" "ab"}, - {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS", - "\xa3" "ab"}, - {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS", - "\xa3" "ab"}, - {"$2x$05$6bNw2HLQYeqHYyBfLMsv/OiwqTymGIGzFsA4hOTWebfehXHNprcAS", - "\xd1\x91"}, - {"$2x$05$6bNw2HLQYeqHYyBfLMsv/O9LIGgn8OMzuDoHfof8AQimSGfcSWxnS", - "\xd0\xc1\xd2\xcf\xcc\xd8"}, - {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6", - "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - "chars after 72 are ignored as usual"}, - {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.R9xrDjiycxMbQE2bp.vgqlYpW5wx2yy", - "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" - "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" - "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" - "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" - "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" - "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"}, - {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.9tQZzcJfm3uj2NvJ/n5xkhpqLrMpWCe", - "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" - "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" - "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" - "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" - "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" - "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"}, - {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy", - ""}, - {"*0", "", "$2a$03$CCCCCCCCCCCCCCCCCCCCC."}, - {"*0", "", "$2a$32$CCCCCCCCCCCCCCCCCCCCC."}, - {"*0", "", "$2c$05$CCCCCCCCCCCCCCCCCCCCC."}, - {"*0", "", "$2z$05$CCCCCCCCCCCCCCCCCCCCC."}, - {"*0", "", "$2`$05$CCCCCCCCCCCCCCCCCCCCC."}, - {"*0", "", "$2{$05$CCCCCCCCCCCCCCCCCCCCC."}, - {"*1", "", "*0"}, - {NULL} -}; - -#define which tests[0] - -static volatile sig_atomic_t running; - -static void handle_timer(int signum) -{ - (void) signum; - running = 0; -} - -static void *run(void *arg) -{ - unsigned long count = 0; - int i = 0; - void *data = NULL; - int size = 0x12345678; - - do { - const char *hash = tests[i][0]; - const char *key = tests[i][1]; - const char *setting = tests[i][2]; - - if (!tests[++i][0]) - i = 0; - - if (setting && strlen(hash) < 30) /* not for benchmark */ - continue; - - if (strcmp(crypt_ra(key, hash, &data, &size), hash)) { - printf("%d: FAILED (crypt_ra/%d/%lu)\n", - (int)((char *)arg - (char *)0), i, count); - free(data); - return NULL; - } - count++; - } while (running); - - free(data); - return count + (char *)0; -} - -int main(void) -{ - struct itimerval it; - struct tms buf; - clock_t clk_tck, start_real, start_virtual, end_real, end_virtual; - unsigned long count; - void *data; - int size; - char *setting1, *setting2; - int i; -#ifdef TEST_THREADS - pthread_t t[TEST_THREADS]; - void *t_retval; -#endif - - data = NULL; - size = 0x12345678; - - for (i = 0; tests[i][0]; i++) { - const char *hash = tests[i][0]; - const char *key = tests[i][1]; - const char *setting = tests[i][2]; - const char *p; - int ok = !setting || strlen(hash) >= 30; - int o_size; - char s_buf[30], o_buf[61]; - if (!setting) { - memcpy(s_buf, hash, sizeof(s_buf) - 1); - s_buf[sizeof(s_buf) - 1] = 0; - setting = s_buf; - } - - __set_errno(0); - p = crypt(key, setting); - if ((!ok && !errno) || strcmp(p, hash)) { - printf("FAILED (crypt/%d)\n", i); - return 1; - } - - if (ok && strcmp(crypt(key, hash), hash)) { - printf("FAILED (crypt/%d)\n", i); - return 1; - } - - for (o_size = -1; o_size <= (int)sizeof(o_buf); o_size++) { - int ok_n = ok && o_size == (int)sizeof(o_buf); - const char *x = "abc"; - strcpy(o_buf, x); - if (o_size >= 3) { - x = "*0"; - if (setting[0] == '*' && setting[1] == '0') - x = "*1"; - } - __set_errno(0); - p = crypt_rn(key, setting, o_buf, o_size); - if ((ok_n && (!p || strcmp(p, hash))) || - (!ok_n && (!errno || p || strcmp(o_buf, x)))) { - printf("FAILED (crypt_rn/%d)\n", i); - return 1; - } - } - - __set_errno(0); - p = crypt_ra(key, setting, &data, &size); - if ((ok && (!p || strcmp(p, hash))) || - (!ok && (!errno || p || strcmp((char *)data, hash)))) { - printf("FAILED (crypt_ra/%d)\n", i); - return 1; - } - } - - setting1 = crypt_gensalt(which[0], 12, data, size); - if (!setting1 || strncmp(setting1, "$2a$12$", 7)) { - puts("FAILED (crypt_gensalt)\n"); - return 1; - } - - setting2 = crypt_gensalt_ra(setting1, 12, data, size); - if (strcmp(setting1, setting2)) { - puts("FAILED (crypt_gensalt_ra/1)\n"); - return 1; - } - - (*(char *)data)++; - setting1 = crypt_gensalt_ra(setting2, 12, data, size); - if (!strcmp(setting1, setting2)) { - puts("FAILED (crypt_gensalt_ra/2)\n"); - return 1; - } - - free(setting1); - free(setting2); - free(data); - -#if defined(_SC_CLK_TCK) || !defined(CLK_TCK) - clk_tck = sysconf(_SC_CLK_TCK); -#else - clk_tck = CLK_TCK; -#endif - - running = 1; - signal(SIGALRM, handle_timer); - - memset(&it, 0, sizeof(it)); - it.it_value.tv_sec = 5; - setitimer(ITIMER_REAL, &it, NULL); - - start_real = times(&buf); - start_virtual = buf.tms_utime + buf.tms_stime; - - count = (char *)run((char *)0) - (char *)0; - - end_real = times(&buf); - end_virtual = buf.tms_utime + buf.tms_stime; - if (end_virtual == start_virtual) end_virtual++; - - printf("%.1f c/s real, %.1f c/s virtual\n", - (float)count * clk_tck / (end_real - start_real), - (float)count * clk_tck / (end_virtual - start_virtual)); - -#ifdef TEST_THREADS - running = 1; - it.it_value.tv_sec = 60; - setitimer(ITIMER_REAL, &it, NULL); - start_real = times(&buf); - - for (i = 0; i < TEST_THREADS; i++) - if (pthread_create(&t[i], NULL, run, i + (char *)0)) { - perror("pthread_create"); - return 1; - } - - for (i = 0; i < TEST_THREADS; i++) { - if (pthread_join(t[i], &t_retval)) { - perror("pthread_join"); - continue; - } - if (!t_retval) continue; - count = (char *)t_retval - (char *)0; - end_real = times(&buf); - printf("%d: %.1f c/s real\n", i, - (float)count * clk_tck / (end_real - start_real)); - } -#endif - - return 0; -} -#endif +/* + * Written by Solar Designer in 2000-2014. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 2000-2014 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * See crypt_blowfish.c for more information. + */ + +#include +#include + +#include +#ifndef __set_errno +#define __set_errno(val) errno = (val) +#endif + +#ifdef TEST +#include +#include +#include +#include +#include +#include +#ifdef TEST_THREADS +#include +#endif +#endif + +#define CRYPT_OUTPUT_SIZE (7 + 22 + 31 + 1) +#define CRYPT_GENSALT_OUTPUT_SIZE (7 + 22 + 1) + +#if defined(__GLIBC__) && defined(_LIBC) +#define __SKIP_GNU +#endif + +#ifdef _WIN32 | _WIN64 +#include "../include/bcrypt/ow-crypt.h" + +#include "../include/bcrypt/crypt_blowfish.h" +#include "../include/bcrypt/crypt_gensalt.h" +#else +#include "ow-crypt.h" + +#include "crypt_blowfish.h" +#include "crypt_gensalt.h" +#endif + +#if defined(__GLIBC__) && defined(_LIBC) +/* crypt.h from glibc-crypt-2.1 will define struct crypt_data for us */ +#include "crypt.h" +extern char *__md5_crypt_r(const char *key, const char *salt, + char *buffer, int buflen); +/* crypt-entry.c needs to be patched to define __des_crypt_r rather than + * __crypt_r, and not define crypt_r and crypt at all */ +extern char *__des_crypt_r(const char *key, const char *salt, + struct crypt_data *data); +extern struct crypt_data _ufc_foobar; +#endif + +static int _crypt_data_alloc(void **data, int *size, int need) +{ + void *updated; + + if (*data && *size >= need) return 0; + + updated = realloc(*data, need); + + if (!updated) { +#ifndef __GLIBC__ + /* realloc(3) on glibc sets errno, so we don't need to bother */ + __set_errno(ENOMEM); +#endif + return -1; + } + +#if defined(__GLIBC__) && defined(_LIBC) + if (need >= sizeof(struct crypt_data)) + ((struct crypt_data *)updated)->initialized = 0; +#endif + + *data = updated; + *size = need; + + return 0; +} + +static char *_crypt_retval_magic(char *retval, const char *setting, + char *output, int size) +{ + if (retval) + return retval; + + if (_crypt_output_magic(setting, output, size)) + return NULL; /* shouldn't happen */ + + return output; +} + +#if defined(__GLIBC__) && defined(_LIBC) +/* + * Applications may re-use the same instance of struct crypt_data without + * resetting the initialized field in order to let crypt_r() skip some of + * its initialization code. Thus, it is important that our multiple hashing + * algorithms either don't conflict with each other in their use of the + * data area or reset the initialized field themselves whenever required. + * Currently, the hashing algorithms simply have no conflicts: the first + * field of struct crypt_data is the 128-byte large DES key schedule which + * __des_crypt_r() calculates each time it is called while the two other + * hashing algorithms use less than 128 bytes of the data area. + */ + +char *__crypt_rn(__const char *key, __const char *setting, + void *data, int size) +{ + if (setting[0] == '$' && setting[1] == '2') + return _crypt_blowfish_rn(key, setting, (char *)data, size); + if (setting[0] == '$' && setting[1] == '1') + return __md5_crypt_r(key, setting, (char *)data, size); + if (setting[0] == '$' || setting[0] == '_') { + __set_errno(EINVAL); + return NULL; + } + if (size >= sizeof(struct crypt_data)) + return __des_crypt_r(key, setting, (struct crypt_data *)data); + __set_errno(ERANGE); + return NULL; +} + +char *__crypt_ra(__const char *key, __const char *setting, + void **data, int *size) +{ + if (setting[0] == '$' && setting[1] == '2') { + if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE)) + return NULL; + return _crypt_blowfish_rn(key, setting, (char *)*data, *size); + } + if (setting[0] == '$' && setting[1] == '1') { + if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE)) + return NULL; + return __md5_crypt_r(key, setting, (char *)*data, *size); + } + if (setting[0] == '$' || setting[0] == '_') { + __set_errno(EINVAL); + return NULL; + } + if (_crypt_data_alloc(data, size, sizeof(struct crypt_data))) + return NULL; + return __des_crypt_r(key, setting, (struct crypt_data *)*data); +} + +char *__crypt_r(__const char *key, __const char *setting, + struct crypt_data *data) +{ + return _crypt_retval_magic( + __crypt_rn(key, setting, data, sizeof(*data)), + setting, (char *)data, sizeof(*data)); +} + +char *__crypt(__const char *key, __const char *setting) +{ + return _crypt_retval_magic( + __crypt_rn(key, setting, &_ufc_foobar, sizeof(_ufc_foobar)), + setting, (char *)&_ufc_foobar, sizeof(_ufc_foobar)); +} +#else +char *crypt_rn(const char *key, const char *setting, void *data, int size) +{ + return _crypt_blowfish_rn(key, setting, (char *)data, size); +} + +char *crypt_ra(const char *key, const char *setting, + void **data, int *size) +{ + if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE)) + return NULL; + return _crypt_blowfish_rn(key, setting, (char *)*data, *size); +} + +char *crypt_r(const char *key, const char *setting, void *data) +{ + return _crypt_retval_magic( + crypt_rn(key, setting, data, CRYPT_OUTPUT_SIZE), + setting, (char *)data, CRYPT_OUTPUT_SIZE); +} + +char *crypt(const char *key, const char *setting) +{ + static char output[CRYPT_OUTPUT_SIZE]; + + return _crypt_retval_magic( + crypt_rn(key, setting, output, sizeof(output)), + setting, output, sizeof(output)); +} + +#define __crypt_gensalt_rn crypt_gensalt_rn +#define __crypt_gensalt_ra crypt_gensalt_ra +#define __crypt_gensalt crypt_gensalt +#endif + +char *__crypt_gensalt_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + char *(*use)(const char *_prefix, unsigned long _count, + const char *_input, int _size, + char *_output, int _output_size); + + /* This may be supported on some platforms in the future */ + if (!input) { + __set_errno(EINVAL); + return NULL; + } + + if (!strncmp(prefix, "$2a$", 4) || !strncmp(prefix, "$2b$", 4) || + !strncmp(prefix, "$2y$", 4)) + use = _crypt_gensalt_blowfish_rn; + else + if (!strncmp(prefix, "$1$", 3)) + use = _crypt_gensalt_md5_rn; + else + if (prefix[0] == '_') + use = _crypt_gensalt_extended_rn; + else + if (!prefix[0] || + (prefix[0] && prefix[1] && + memchr(_crypt_itoa64, prefix[0], 64) && + memchr(_crypt_itoa64, prefix[1], 64))) + use = _crypt_gensalt_traditional_rn; + else { + __set_errno(EINVAL); + return NULL; + } + + return use(prefix, count, input, size, output, output_size); +} + +char *__crypt_gensalt_ra(const char *prefix, unsigned long count, + const char *input, int size) +{ + char output[CRYPT_GENSALT_OUTPUT_SIZE]; + char *retval; + + retval = __crypt_gensalt_rn(prefix, count, + input, size, output, sizeof(output)); + + if (retval) { +#ifdef _WIN32 | _WIN64 + retval = _strdup(retval); +#else + retval = strdup(retval); +#endif +#ifndef __GLIBC__ + /* strdup(3) on glibc sets errno, so we don't need to bother */ + if (!retval) + __set_errno(ENOMEM); +#endif + } + + return retval; +} + +char *__crypt_gensalt(const char *prefix, unsigned long count, + const char *input, int size) +{ + static char output[CRYPT_GENSALT_OUTPUT_SIZE]; + + return __crypt_gensalt_rn(prefix, count, + input, size, output, sizeof(output)); +} + +#if defined(__GLIBC__) && defined(_LIBC) +weak_alias(__crypt_rn, crypt_rn) +weak_alias(__crypt_ra, crypt_ra) +weak_alias(__crypt_r, crypt_r) +weak_alias(__crypt, crypt) +weak_alias(__crypt_gensalt_rn, crypt_gensalt_rn) +weak_alias(__crypt_gensalt_ra, crypt_gensalt_ra) +weak_alias(__crypt_gensalt, crypt_gensalt) +weak_alias(crypt, fcrypt) +#endif + +#ifdef TEST +static const char *tests[][3] = { + {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW", + "U*U"}, + {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK", + "U*U*"}, + {"$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a", + "U*U*U"}, + {"$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui", + "0123456789abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + "chars after 72 are ignored"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", + "\xa3"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", + "\xff\xff\xa3"}, + {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", + "\xff\xff\xa3"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.nqd1wy.pTMdcvrRWxyiGL2eMz.2a85.", + "\xff\xff\xa3"}, + {"$2b$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", + "\xff\xff\xa3"}, + {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq", + "\xa3"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq", + "\xa3"}, + {"$2b$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq", + "\xa3"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", + "1\xa3" "345"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", + "\xff\xa3" "345"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", + "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"}, + {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", + "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.ZC1JEJ8Z4gPfpe1JOr/oyPXTWl9EFd.", + "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"}, + {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e", + "\xff\xa3" "345"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e", + "\xff\xa3" "345"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS", + "\xa3" "ab"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS", + "\xa3" "ab"}, + {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS", + "\xa3" "ab"}, + {"$2x$05$6bNw2HLQYeqHYyBfLMsv/OiwqTymGIGzFsA4hOTWebfehXHNprcAS", + "\xd1\x91"}, + {"$2x$05$6bNw2HLQYeqHYyBfLMsv/O9LIGgn8OMzuDoHfof8AQimSGfcSWxnS", + "\xd0\xc1\xd2\xcf\xcc\xd8"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6", + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "chars after 72 are ignored as usual"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.R9xrDjiycxMbQE2bp.vgqlYpW5wx2yy", + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.9tQZzcJfm3uj2NvJ/n5xkhpqLrMpWCe", + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"}, + {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy", + ""}, + {"*0", "", "$2a$03$CCCCCCCCCCCCCCCCCCCCC."}, + {"*0", "", "$2a$32$CCCCCCCCCCCCCCCCCCCCC."}, + {"*0", "", "$2c$05$CCCCCCCCCCCCCCCCCCCCC."}, + {"*0", "", "$2z$05$CCCCCCCCCCCCCCCCCCCCC."}, + {"*0", "", "$2`$05$CCCCCCCCCCCCCCCCCCCCC."}, + {"*0", "", "$2{$05$CCCCCCCCCCCCCCCCCCCCC."}, + {"*1", "", "*0"}, + {NULL} +}; + +#define which tests[0] + +static volatile sig_atomic_t running; + +static void handle_timer(int signum) +{ + (void) signum; + running = 0; +} + +static void *run(void *arg) +{ + unsigned long count = 0; + int i = 0; + void *data = NULL; + int size = 0x12345678; + + do { + const char *hash = tests[i][0]; + const char *key = tests[i][1]; + const char *setting = tests[i][2]; + + if (!tests[++i][0]) + i = 0; + + if (setting && strlen(hash) < 30) /* not for benchmark */ + continue; + + if (strcmp(crypt_ra(key, hash, &data, &size), hash)) { + printf("%d: FAILED (crypt_ra/%d/%lu)\n", + (int)((char *)arg - (char *)0), i, count); + free(data); + return NULL; + } + count++; + } while (running); + + free(data); + return count + (char *)0; +} + +int main(void) +{ + struct itimerval it; + struct tms buf; + clock_t clk_tck, start_real, start_virtual, end_real, end_virtual; + unsigned long count; + void *data; + int size; + char *setting1, *setting2; + int i; +#ifdef TEST_THREADS + pthread_t t[TEST_THREADS]; + void *t_retval; +#endif + + data = NULL; + size = 0x12345678; + + for (i = 0; tests[i][0]; i++) { + const char *hash = tests[i][0]; + const char *key = tests[i][1]; + const char *setting = tests[i][2]; + const char *p; + int ok = !setting || strlen(hash) >= 30; + int o_size; + char s_buf[30], o_buf[61]; + if (!setting) { + memcpy(s_buf, hash, sizeof(s_buf) - 1); + s_buf[sizeof(s_buf) - 1] = 0; + setting = s_buf; + } + + __set_errno(0); + p = crypt(key, setting); + if ((!ok && !errno) || strcmp(p, hash)) { + printf("FAILED (crypt/%d)\n", i); + return 1; + } + + if (ok && strcmp(crypt(key, hash), hash)) { + printf("FAILED (crypt/%d)\n", i); + return 1; + } + + for (o_size = -1; o_size <= (int)sizeof(o_buf); o_size++) { + int ok_n = ok && o_size == (int)sizeof(o_buf); + const char *x = "abc"; + strcpy(o_buf, x); + if (o_size >= 3) { + x = "*0"; + if (setting[0] == '*' && setting[1] == '0') + x = "*1"; + } + __set_errno(0); + p = crypt_rn(key, setting, o_buf, o_size); + if ((ok_n && (!p || strcmp(p, hash))) || + (!ok_n && (!errno || p || strcmp(o_buf, x)))) { + printf("FAILED (crypt_rn/%d)\n", i); + return 1; + } + } + + __set_errno(0); + p = crypt_ra(key, setting, &data, &size); + if ((ok && (!p || strcmp(p, hash))) || + (!ok && (!errno || p || strcmp((char *)data, hash)))) { + printf("FAILED (crypt_ra/%d)\n", i); + return 1; + } + } + + setting1 = crypt_gensalt(which[0], 12, data, size); + if (!setting1 || strncmp(setting1, "$2a$12$", 7)) { + puts("FAILED (crypt_gensalt)\n"); + return 1; + } + + setting2 = crypt_gensalt_ra(setting1, 12, data, size); + if (strcmp(setting1, setting2)) { + puts("FAILED (crypt_gensalt_ra/1)\n"); + return 1; + } + + (*(char *)data)++; + setting1 = crypt_gensalt_ra(setting2, 12, data, size); + if (!strcmp(setting1, setting2)) { + puts("FAILED (crypt_gensalt_ra/2)\n"); + return 1; + } + + free(setting1); + free(setting2); + free(data); + +#if defined(_SC_CLK_TCK) || !defined(CLK_TCK) + clk_tck = sysconf(_SC_CLK_TCK); +#else + clk_tck = CLK_TCK; +#endif + + running = 1; + signal(SIGALRM, handle_timer); + + memset(&it, 0, sizeof(it)); + it.it_value.tv_sec = 5; + setitimer(ITIMER_REAL, &it, NULL); + + start_real = times(&buf); + start_virtual = buf.tms_utime + buf.tms_stime; + + count = (char *)run((char *)0) - (char *)0; + + end_real = times(&buf); + end_virtual = buf.tms_utime + buf.tms_stime; + if (end_virtual == start_virtual) end_virtual++; + + printf("%.1f c/s real, %.1f c/s virtual\n", + (float)count * clk_tck / (end_real - start_real), + (float)count * clk_tck / (end_virtual - start_virtual)); + +#ifdef TEST_THREADS + running = 1; + it.it_value.tv_sec = 60; + setitimer(ITIMER_REAL, &it, NULL); + start_real = times(&buf); + + for (i = 0; i < TEST_THREADS; i++) + if (pthread_create(&t[i], NULL, run, i + (char *)0)) { + perror("pthread_create"); + return 1; + } + + for (i = 0; i < TEST_THREADS; i++) { + if (pthread_join(t[i], &t_retval)) { + perror("pthread_join"); + continue; + } + if (!t_retval) continue; + count = (char *)t_retval - (char *)0; + end_real = times(&buf); + printf("%d: %.1f c/s real\n", i, + (float)count * clk_tck / (end_real - start_real)); + } +#endif + + return 0; +} +#endif diff --git a/src/libbcrypt/src/x86.S b/dep/libbcrypt/src/x86.S similarity index 100% rename from src/libbcrypt/src/x86.S rename to dep/libbcrypt/src/x86.S diff --git a/src/libbcrypt/vs2017/libbcrypt/libbcrypt.sln b/dep/libbcrypt/vs2017/libbcrypt/libbcrypt.sln similarity index 98% rename from src/libbcrypt/vs2017/libbcrypt/libbcrypt.sln rename to dep/libbcrypt/vs2017/libbcrypt/libbcrypt.sln index 38c2d4047..70256fc2d 100644 --- a/src/libbcrypt/vs2017/libbcrypt/libbcrypt.sln +++ b/dep/libbcrypt/vs2017/libbcrypt/libbcrypt.sln @@ -1,41 +1,41 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.136 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libbcrypt", "libbcrypt.vcxproj", "{D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "..\test\test.vcxproj", "{C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x64.ActiveCfg = Debug|x64 - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x64.Build.0 = Debug|x64 - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x86.ActiveCfg = Debug|Win32 - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x86.Build.0 = Debug|Win32 - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x64.ActiveCfg = Release|x64 - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x64.Build.0 = Release|x64 - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x86.ActiveCfg = Release|Win32 - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x86.Build.0 = Release|Win32 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x64.ActiveCfg = Debug|x64 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x64.Build.0 = Debug|x64 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x86.ActiveCfg = Debug|Win32 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x86.Build.0 = Debug|Win32 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x64.ActiveCfg = Release|x64 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x64.Build.0 = Release|x64 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x86.ActiveCfg = Release|Win32 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {2DB786F9-9679-4A72-A4A0-2544E42B78CB} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.136 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libbcrypt", "libbcrypt.vcxproj", "{D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "..\test\test.vcxproj", "{C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x64.ActiveCfg = Debug|x64 + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x64.Build.0 = Debug|x64 + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x86.ActiveCfg = Debug|Win32 + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x86.Build.0 = Debug|Win32 + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x64.ActiveCfg = Release|x64 + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x64.Build.0 = Release|x64 + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x86.ActiveCfg = Release|Win32 + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x86.Build.0 = Release|Win32 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x64.ActiveCfg = Debug|x64 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x64.Build.0 = Debug|x64 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x86.ActiveCfg = Debug|Win32 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x86.Build.0 = Debug|Win32 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x64.ActiveCfg = Release|x64 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x64.Build.0 = Release|x64 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x86.ActiveCfg = Release|Win32 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2DB786F9-9679-4A72-A4A0-2544E42B78CB} + EndGlobalSection +EndGlobal diff --git a/src/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj b/dep/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj similarity index 97% rename from src/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj rename to dep/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj index 577af5635..e97c78180 100644 --- a/src/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj +++ b/dep/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj @@ -1,135 +1,135 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4} - libbcrypt - 10.0.17763.0 - - - - StaticLibrary - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - - - Level3 - Disabled - true - true - - - - - Level3 - Disabled - true - true - - - - - Level3 - MaxSpeed - true - true - true - true - - - true - true - - - - - Level3 - MaxSpeed - true - true - true - true - - - true - true - - - - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4} + libbcrypt + 10.0.17763.0 + + + + StaticLibrary + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + Application + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + true + + + + + Level3 + Disabled + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj.filters b/dep/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj.filters similarity index 97% rename from src/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj.filters rename to dep/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj.filters index e30024695..8edc396c8 100644 --- a/src/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj.filters +++ b/dep/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj.filters @@ -1,54 +1,54 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + \ No newline at end of file diff --git a/src/libbcrypt/vs2017/test/main.cpp b/dep/libbcrypt/vs2017/test/main.cpp similarity index 96% rename from src/libbcrypt/vs2017/test/main.cpp rename to dep/libbcrypt/vs2017/test/main.cpp index fac9b7134..fa8994b20 100644 --- a/src/libbcrypt/vs2017/test/main.cpp +++ b/dep/libbcrypt/vs2017/test/main.cpp @@ -1,22 +1,22 @@ -#include "../../include/bcrypt/BCrypt.hpp" -#include - -using namespace std; - -int main() { - string right_password = "right_password"; - string wrong_password = "wrong_password"; - - cout << "generate hash... " << flush; - string hash = BCrypt::generateHash(right_password, 12); - cout << "done." << endl; - - cout << "checking right password: " << flush - << BCrypt::validatePassword(right_password, hash) << endl; - - cout << "checking wrong password: " << flush - << BCrypt::validatePassword(wrong_password, hash) << endl; - - system("pause"); - return 0; -} +#include "../../include/bcrypt/BCrypt.hpp" +#include + +using namespace std; + +int main() { + string right_password = "right_password"; + string wrong_password = "wrong_password"; + + cout << "generate hash... " << flush; + string hash = BCrypt::generateHash(right_password, 12); + cout << "done." << endl; + + cout << "checking right password: " << flush + << BCrypt::validatePassword(right_password, hash) << endl; + + cout << "checking wrong password: " << flush + << BCrypt::validatePassword(wrong_password, hash) << endl; + + system("pause"); + return 0; +} diff --git a/src/libbcrypt/vs2017/test/test.vcxproj b/dep/libbcrypt/vs2017/test/test.vcxproj similarity index 97% rename from src/libbcrypt/vs2017/test/test.vcxproj rename to dep/libbcrypt/vs2017/test/test.vcxproj index a05b3cf6e..566addc6b 100644 --- a/src/libbcrypt/vs2017/test/test.vcxproj +++ b/dep/libbcrypt/vs2017/test/test.vcxproj @@ -1,131 +1,131 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - - - - - {d6a9a3f3-1312-4494-85b8-7ce7dd4d78f4} - - - - 15.0 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68} - test - 10.0.17763.0 - - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - - - Level3 - Disabled - true - true - - - ../libbcrypt/Debug/libbcrypt.lib;%(AdditionalDependencies) - - - - - Level3 - Disabled - true - true - - - - - Level3 - MaxSpeed - true - true - true - true - - - true - true - - - - - Level3 - MaxSpeed - true - true - true - true - - - true - true - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + {d6a9a3f3-1312-4494-85b8-7ce7dd4d78f4} + + + + 15.0 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68} + test + 10.0.17763.0 + + + + Application + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + Application + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + true + + + ../libbcrypt/Debug/libbcrypt.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + + + + + \ No newline at end of file diff --git a/src/libbcrypt/vs2017/test/test.vcxproj.filters b/dep/libbcrypt/vs2017/test/test.vcxproj.filters similarity index 96% rename from src/libbcrypt/vs2017/test/test.vcxproj.filters rename to dep/libbcrypt/vs2017/test/test.vcxproj.filters index 128a38664..a91ad0ed9 100644 --- a/src/libbcrypt/vs2017/test/test.vcxproj.filters +++ b/dep/libbcrypt/vs2017/test/test.vcxproj.filters @@ -1,22 +1,22 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + \ No newline at end of file diff --git a/dep/span-lite/CMakeLists.txt b/dep/span-lite/CMakeLists.txt new file mode 100644 index 000000000..4d4f9d43f --- /dev/null +++ b/dep/span-lite/CMakeLists.txt @@ -0,0 +1,5 @@ +add_library(span-lite INTERFACE) +add_library(martinmoene::span-lite ALIAS span-lite) + +target_include_directories(span-lite INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/dep/span-lite/LICENSE.txt b/dep/span-lite/LICENSE.txt new file mode 100644 index 000000000..36b7cd93c --- /dev/null +++ b/dep/span-lite/LICENSE.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/dep/span-lite/README.md b/dep/span-lite/README.md new file mode 100644 index 000000000..4b077c889 --- /dev/null +++ b/dep/span-lite/README.md @@ -0,0 +1,514 @@ + +# span lite: A single-file header-only version of a C++20-like span for C++98, C++11 and later + +[![Language](https://img.shields.io/badge/C%2B%2B-98/11/14/17/20-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) [![License](https://img.shields.io/badge/license-BSL-blue.svg)](https://opensource.org/licenses/BSL-1.0) [![Build Status](https://travis-ci.org/martinmoene/span-lite.svg?branch=master)](https://travis-ci.org/martinmoene/span-lite) [![Build status](https://ci.appveyor.com/api/projects/status/1ha3wnxtam547m8p?svg=true)](https://ci.appveyor.com/project/martinmoene/span-lite) [![Version](https://badge.fury.io/gh/martinmoene%2Fspan-lite.svg)](https://github.com/martinmoene/span-lite/releases) [![download](https://img.shields.io/badge/latest-download-blue.svg)](https://github.com/martinmoene/span-lite/blob/master/include/nonstd/span.hpp) [![Conan](https://img.shields.io/badge/on-conan-blue.svg)](https://conan.io/center/span-lite) [![Try it on wandbox](https://img.shields.io/badge/on-wandbox-blue.svg)](https://wandbox.org/permlink/venR3Ko2Q4tlvcVk) [![Try it on godbolt online](https://img.shields.io/badge/on-godbolt-blue.svg)](https://godbolt.org/z/htwpnb) + +**Contents** + +- [Example usage](#example-usage) +- [In a nutshell](#in-a-nutshell) +- [License](#license) +- [Dependencies](#dependencies) +- [Installation and use](#installation-and-use) +- [Synopsis](#synopsis) +- [Reported to work with](#reported-to-work-with) +- [Building the tests](#building-the-tests) +- [Other implementations of span](#other-implementations-of-span) +- [Notes and references](#notes-and-references) +- [Appendix](#appendix) + +## Example usage + +```cpp +#include "nonstd/span.hpp" +#include +#include +#include + +std::ptrdiff_t size( nonstd::span spn ) +{ + return spn.size(); +} + +int main() +{ + int arr[] = { 1, }; + + std::cout << + "C-array:" << size( arr ) << + " array:" << size( std::array { 1, 2, } ) << + " vector:" << size( std::vector{ 1, 2, 3, } ); +} +``` + +### Compile and run + +```bash +prompt> g++ -std=c++11 -Wall -I../include -o 01-basic.exe 01-basic.cpp && 01-basic.exe +C-array:1 array:2 vector:3 +``` + +## In a nutshell + +**span lite** is a single-file header-only library to provide a bounds-safe view for sequences of objects. The library provides a [C++20-like span](http://en.cppreference.com/w/cpp/container/span) for use with C++98 and later. If available, `std::span` is used, unless [configured otherwise](#configuration). *span-lite* can detect the presence of [*byte-lite*](https://github.com/martinmoene/byte-lite) and if present, it provides `as_bytes()` and `as_writable_bytes()` also for C++14 and earlier. + +**Features and properties of span lite** are ease of installation (single header), freedom of dependencies other than the standard library. To compensate for the class template argument deduction that is missing from pre-C++17 compilers, `nonstd::span` can provide `make_span` functions. See [configuration](#configuration). + +## License + +*span lite* is distributed under the [Boost Software License](https://github.com/martinmoene/span-lite/blob/master/LICENSE.txt). + +## Dependencies + +*span lite* has no other dependencies than the [C++ standard library](http://en.cppreference.com/w/cpp/header). + +## Installation and use + +*span lite* is a single-file header-only library. Put `span.hpp` in the [include](include) folder directly into the project source tree or somewhere reachable from your project. + +## Synopsis + +**Contents** +[Documentation of `std::span`](#documentation-of-stdspan) +[Later additions](#later-additions) +[Non-standard extensions](#non-standard-extensions) +[Configuration](#configuration) + +## Documentation of `std::span` + +Depending on the compiler and C++-standard used, `nonstd::span` behaves less or more like `std::span`. To get an idea of the capabilities of `nonstd::span` with your configuration, look at the output of the [tests](test/span.t.cpp), issuing `span-main.t --pass @`. For `std::span`, see its [documentation at cppreference](http://en.cppreference.com/w/cpp/container/span). + +## Later additions + +### `back()` and `front()` + +*span lite* can provide `back()` and `front()` member functions for element access. See the table below and section [configuration](#configuration). + +## Non-standard extensions + +### Construct from container + +To construct a span from a container with compilers that cannot constrain such a single-parameter constructor to containers, *span lite* provides a constructor that takes an additional parameter of type `with_container_t`. Use `with_container` as value for this parameter. See the table below and section [configuration](#configuration). + +### Construct from `std::array` with const data + +*span lite* can provide construction of a span from a `std::array` with const data. See the table below and section [configuration](#configuration). + +### `operator()` + +*span lite* can provide member function call `operator()` for element access. It is equivalent to `operator[]` and has been marked `[[deprecated]]`. Its main purpose is to provide a migration path. + +### `at()` + +*span lite* can provide member function `at()` for element access. Unless exceptions have been disabled, `at()` throws std::out_of_range if the index falls outside the span. With exceptions disabled, `at(index_t)` delegates bounds checking to `operator[](index_t)`. See the table below and sections [configuration](#configuration) and [disable exceptions](#disable-exceptions). + +### `swap()` + +*span lite* can provide a `swap()`member function. See the table below and section [configuration](#configuration). + +### `operator==()` and other comparison functions + +*span lite* can provide functions to compare the content of two spans. However, C++20's span will not provide comparison and _span lite_ will omit comparison at default in the near future. See the table below and section [configuration](#configuration). See also [Revisiting Regular Types](#regtyp). + +### `same()` + +*span lite* can provide function `same()` to determine if two spans refer as identical spans to the same data via the same type. If `same()` is enabled, `operator==()` incorporates it in its comparison. See the table below and section [configuration](#configuration). + +### `first()`, `last()` and `subspan()` + +*span lite* can provide functions `first()`, `last()` and `subspan()` to avoid having to use the *dot template* syntax when the span is a dependent type. See the table below and section [configuration](#configuration). + +### `make_span()` + +*span lite* can provide `make_span()` creator functions to compensate for the class template argument deduction that is missing from pre-C++17 compilers. See the table below and section [configuration](#configuration). + +### `byte_span()` + +*span lite* can provide `byte_span()` creator functions to represent an object as a span of bytes. This requires the C++17 type `std::byte` to be available. See the table below and section [configuration](#configuration). + +| Kind | std | Function or method | +|--------------------|------|--------------------| +| **Macro** | | macro **`span_FEATURE_WITH_CONTAINER`**
macro **`span_FEATURE_WITH_CONTAINER_TO_STD`** | +| **Types** | | **with_container_t** type to disambiguate below constructors | +| **Objects** | | **with_container** value to disambiguate below constructors | +| **Constructors** | | macro **`span_FEATURE_CONSTRUCTION_FROM_STDARRAY_ELEMENT_TYPE`**| +|   | | template<class Container>
constexpr **span**(with_container_t, Container & cont) | +|   | | template<class Container>
constexpr **span**(with_container_t, Container const & cont) | +|   | |   | +| **Methods** | | macro **`span_FEATURE_MEMBER_CALL_OPERATOR`** | +|   | | constexpr reference **operator()**(index_t idx) const
Equivalent to **operator[]**(), marked `[[deprecated]]` | +|   | |   | +| **Methods** | | macro **`span_FEATURE_MEMBER_AT`** | +|   | | constexpr reference **at**(index_t idx) const
May throw std::out_of_range exception | +|   | |   | +| **Methods** | | macro **`span_FEATURE_MEMBER_BACK_FRONT`** (on since v0.5.0) | +|   | | constexpr reference **back()** const noexcept | +|   | | constexpr reference **front()** const noexcept | +|   | |   | +| **Method** | | macro **`span_FEATURE_MEMBER_SWAP`** | +|   | | constexpr void **swap**(span & other) noexcept | +|   | |   | +| **Free functions** | | macro **`span_FEATURE_COMPARISON`** | +|

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

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

+
diff --git a/dep/span-lite/include/span.hpp b/dep/span-lite/include/span.hpp new file mode 100644 index 000000000..8ec88e0ad --- /dev/null +++ b/dep/span-lite/include/span.hpp @@ -0,0 +1,1817 @@ +// +// span for C++98 and later. +// Based on http://wg21.link/p0122r7 +// For more information see https://github.com/martinmoene/span-lite +// +// Copyright 2018-2020 Martin Moene +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef NONSTD_SPAN_HPP_INCLUDED +#define NONSTD_SPAN_HPP_INCLUDED + +#define span_lite_MAJOR 0 +#define span_lite_MINOR 9 +#define span_lite_PATCH 2 + +#define span_lite_VERSION span_STRINGIFY(span_lite_MAJOR) "." span_STRINGIFY(span_lite_MINOR) "." span_STRINGIFY(span_lite_PATCH) + +#define span_STRINGIFY( x ) span_STRINGIFY_( x ) +#define span_STRINGIFY_( x ) #x + +// span configuration: + +#define span_SPAN_DEFAULT 0 +#define span_SPAN_NONSTD 1 +#define span_SPAN_STD 2 + +// tweak header support: + +#ifdef __has_include +# if __has_include() +# include +# endif +#define span_HAVE_TWEAK_HEADER 1 +#else +#define span_HAVE_TWEAK_HEADER 0 +//# pragma message("span.hpp: Note: Tweak header not supported.") +#endif + +// span selection and configuration: + +#define span_HAVE( feature ) ( span_HAVE_##feature ) + +#ifndef span_CONFIG_SELECT_SPAN +# define span_CONFIG_SELECT_SPAN ( span_HAVE_STD_SPAN ? span_SPAN_STD : span_SPAN_NONSTD ) +#endif + +#ifndef span_CONFIG_EXTENT_TYPE +# define span_CONFIG_EXTENT_TYPE std::size_t +#endif + +#ifndef span_CONFIG_SIZE_TYPE +# define span_CONFIG_SIZE_TYPE std::size_t +#endif + +#ifdef span_CONFIG_INDEX_TYPE +# error `span_CONFIG_INDEX_TYPE` is deprecated since v0.7.0; it is replaced by `span_CONFIG_SIZE_TYPE`. +#endif + +// span configuration (features): + +#ifndef span_FEATURE_WITH_CONTAINER +#ifdef span_FEATURE_WITH_CONTAINER_TO_STD +# define span_FEATURE_WITH_CONTAINER span_IN_STD( span_FEATURE_WITH_CONTAINER_TO_STD ) +#else +# define span_FEATURE_WITH_CONTAINER 0 +#endif +#endif + +#ifndef span_FEATURE_CONSTRUCTION_FROM_STDARRAY_ELEMENT_TYPE +# define span_FEATURE_CONSTRUCTION_FROM_STDARRAY_ELEMENT_TYPE 0 +#endif + +#ifndef span_FEATURE_MEMBER_AT +# define span_FEATURE_MEMBER_AT 0 +#endif + +#ifndef span_FEATURE_MEMBER_BACK_FRONT +# define span_FEATURE_MEMBER_BACK_FRONT 1 +#endif + +#ifndef span_FEATURE_MEMBER_CALL_OPERATOR +# define span_FEATURE_MEMBER_CALL_OPERATOR 0 +#endif + +#ifndef span_FEATURE_MEMBER_SWAP +# define span_FEATURE_MEMBER_SWAP 0 +#endif + +#ifndef span_FEATURE_NON_MEMBER_FIRST_LAST_SUB +# define span_FEATURE_NON_MEMBER_FIRST_LAST_SUB 0 +#endif + +#ifndef span_FEATURE_COMPARISON +# define span_FEATURE_COMPARISON 0 // Note: C++20 does not provide comparison +#endif + +#ifndef span_FEATURE_SAME +# define span_FEATURE_SAME 0 +#endif + +#if span_FEATURE_SAME && !span_FEATURE_COMPARISON +# error `span_FEATURE_SAME` requires `span_FEATURE_COMPARISON` +#endif + +#ifndef span_FEATURE_MAKE_SPAN +#ifdef span_FEATURE_MAKE_SPAN_TO_STD +# define span_FEATURE_MAKE_SPAN span_IN_STD( span_FEATURE_MAKE_SPAN_TO_STD ) +#else +# define span_FEATURE_MAKE_SPAN 0 +#endif +#endif + +#ifndef span_FEATURE_BYTE_SPAN +# define span_FEATURE_BYTE_SPAN 0 +#endif + +// Control presence of exception handling (try and auto discover): + +#ifndef span_CONFIG_NO_EXCEPTIONS +# if _MSC_VER +# include // for _HAS_EXCEPTIONS +# endif +# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS) +# define span_CONFIG_NO_EXCEPTIONS 0 +# else +# define span_CONFIG_NO_EXCEPTIONS 1 +# undef span_CONFIG_CONTRACT_VIOLATION_THROWS +# undef span_CONFIG_CONTRACT_VIOLATION_TERMINATES +# define span_CONFIG_CONTRACT_VIOLATION_THROWS 0 +# define span_CONFIG_CONTRACT_VIOLATION_TERMINATES 1 +# endif +#endif + +// Control pre- and postcondition violation behaviour: + +#if defined( span_CONFIG_CONTRACT_LEVEL_ON ) +# define span_CONFIG_CONTRACT_LEVEL_MASK 0x11 +#elif defined( span_CONFIG_CONTRACT_LEVEL_OFF ) +# define span_CONFIG_CONTRACT_LEVEL_MASK 0x00 +#elif defined( span_CONFIG_CONTRACT_LEVEL_EXPECTS_ONLY ) +# define span_CONFIG_CONTRACT_LEVEL_MASK 0x01 +#elif defined( span_CONFIG_CONTRACT_LEVEL_ENSURES_ONLY ) +# define span_CONFIG_CONTRACT_LEVEL_MASK 0x10 +#else +# define span_CONFIG_CONTRACT_LEVEL_MASK 0x11 +#endif + +#if defined( span_CONFIG_CONTRACT_VIOLATION_THROWS ) +# define span_CONFIG_CONTRACT_VIOLATION_THROWS_V span_CONFIG_CONTRACT_VIOLATION_THROWS +#else +# define span_CONFIG_CONTRACT_VIOLATION_THROWS_V 0 +#endif + +#if defined( span_CONFIG_CONTRACT_VIOLATION_THROWS ) && span_CONFIG_CONTRACT_VIOLATION_THROWS && \ + defined( span_CONFIG_CONTRACT_VIOLATION_TERMINATES ) && span_CONFIG_CONTRACT_VIOLATION_TERMINATES +# error Please define none or one of span_CONFIG_CONTRACT_VIOLATION_THROWS and span_CONFIG_CONTRACT_VIOLATION_TERMINATES to 1, but not both. +#endif + +// C++ language version detection (C++20 is speculative): +// Note: VC14.0/1900 (VS2015) lacks too much from C++14. + +#ifndef span_CPLUSPLUS +# if defined(_MSVC_LANG ) && !defined(__clang__) +# define span_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) +# else +# define span_CPLUSPLUS __cplusplus +# endif +#endif + +#define span_CPP98_OR_GREATER ( span_CPLUSPLUS >= 199711L ) +#define span_CPP11_OR_GREATER ( span_CPLUSPLUS >= 201103L ) +#define span_CPP14_OR_GREATER ( span_CPLUSPLUS >= 201402L ) +#define span_CPP17_OR_GREATER ( span_CPLUSPLUS >= 201703L ) +#define span_CPP20_OR_GREATER ( span_CPLUSPLUS >= 202000L ) + +// C++ language version (represent 98 as 3): + +#define span_CPLUSPLUS_V ( span_CPLUSPLUS / 100 - (span_CPLUSPLUS > 200000 ? 2000 : 1994) ) + +#define span_IN_STD( v ) ( ((v) == 98 ? 3 : (v)) >= span_CPLUSPLUS_V ) + +#define span_CONFIG( feature ) ( span_CONFIG_##feature ) +#define span_FEATURE( feature ) ( span_FEATURE_##feature ) +#define span_FEATURE_TO_STD( feature ) ( span_IN_STD( span_FEATURE( feature##_TO_STD ) ) ) + +// Use C++20 std::span if available and requested: + +#if span_CPP20_OR_GREATER && defined(__has_include ) +# if __has_include( ) +# define span_HAVE_STD_SPAN 1 +# else +# define span_HAVE_STD_SPAN 0 +# endif +#else +# define span_HAVE_STD_SPAN 0 +#endif + +#define span_USES_STD_SPAN ( (span_CONFIG_SELECT_SPAN == span_SPAN_STD) || ((span_CONFIG_SELECT_SPAN == span_SPAN_DEFAULT) && span_HAVE_STD_SPAN) ) + +// +// Use C++20 std::span: +// + +#if span_USES_STD_SPAN + +#include + +namespace nonstd { + +using std::span; + +// Note: C++20 does not provide comparison +// using std::operator==; +// using std::operator!=; +// using std::operator<; +// using std::operator<=; +// using std::operator>; +// using std::operator>=; +} // namespace nonstd + +#else // span_USES_STD_SPAN + +#include + +// Compiler versions: +// +// MSVC++ 6.0 _MSC_VER == 1200 span_COMPILER_MSVC_VERSION == 60 (Visual Studio 6.0) +// MSVC++ 7.0 _MSC_VER == 1300 span_COMPILER_MSVC_VERSION == 70 (Visual Studio .NET 2002) +// MSVC++ 7.1 _MSC_VER == 1310 span_COMPILER_MSVC_VERSION == 71 (Visual Studio .NET 2003) +// MSVC++ 8.0 _MSC_VER == 1400 span_COMPILER_MSVC_VERSION == 80 (Visual Studio 2005) +// MSVC++ 9.0 _MSC_VER == 1500 span_COMPILER_MSVC_VERSION == 90 (Visual Studio 2008) +// MSVC++ 10.0 _MSC_VER == 1600 span_COMPILER_MSVC_VERSION == 100 (Visual Studio 2010) +// MSVC++ 11.0 _MSC_VER == 1700 span_COMPILER_MSVC_VERSION == 110 (Visual Studio 2012) +// MSVC++ 12.0 _MSC_VER == 1800 span_COMPILER_MSVC_VERSION == 120 (Visual Studio 2013) +// MSVC++ 14.0 _MSC_VER == 1900 span_COMPILER_MSVC_VERSION == 140 (Visual Studio 2015) +// MSVC++ 14.1 _MSC_VER >= 1910 span_COMPILER_MSVC_VERSION == 141 (Visual Studio 2017) +// MSVC++ 14.2 _MSC_VER >= 1920 span_COMPILER_MSVC_VERSION == 142 (Visual Studio 2019) + +#if defined(_MSC_VER ) && !defined(__clang__) +# define span_COMPILER_MSVC_VER (_MSC_VER ) +# define span_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) ) +#else +# define span_COMPILER_MSVC_VER 0 +# define span_COMPILER_MSVC_VERSION 0 +#endif + +#define span_COMPILER_VERSION( major, minor, patch ) ( 10 * ( 10 * (major) + (minor) ) + (patch) ) + +#if defined(__clang__) +# define span_COMPILER_CLANG_VERSION span_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) +#else +# define span_COMPILER_CLANG_VERSION 0 +#endif + +#if defined(__GNUC__) && !defined(__clang__) +# define span_COMPILER_GNUC_VERSION span_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#else +# define span_COMPILER_GNUC_VERSION 0 +#endif + +// half-open range [lo..hi): +#define span_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) ) + +// Compiler warning suppression: + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wmismatched-tags" +# define span_RESTORE_WARNINGS() _Pragma( "clang diagnostic pop" ) + +#elif defined __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wundef" +# define span_RESTORE_WARNINGS() _Pragma( "GCC diagnostic pop" ) + +#elif span_COMPILER_MSVC_VER >= 1900 +# define span_DISABLE_MSVC_WARNINGS(codes) __pragma(warning(push)) __pragma(warning(disable: codes)) +# define span_RESTORE_WARNINGS() __pragma(warning(pop )) + +// Suppress the following MSVC GSL warnings: +// - C26439, gsl::f.6 : special function 'function' can be declared 'noexcept' +// - C26440, gsl::f.6 : function 'function' can be declared 'noexcept' +// - C26472, gsl::t.1 : don't use a static_cast for arithmetic conversions; +// use brace initialization, gsl::narrow_cast or gsl::narrow +// - C26473: gsl::t.1 : don't cast between pointer types where the source type and the target type are the same +// - C26481: gsl::b.1 : don't use pointer arithmetic. Use span instead +// - C26490: gsl::t.1 : don't use reinterpret_cast + +span_DISABLE_MSVC_WARNINGS( 26439 26440 26472 26473 26481 26490 ) + +#else +# define span_RESTORE_WARNINGS() /*empty*/ +#endif + +// Presence of language and library features: + +#ifdef _HAS_CPP0X +# define span_HAS_CPP0X _HAS_CPP0X +#else +# define span_HAS_CPP0X 0 +#endif + +#define span_CPP11_80 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1400) +#define span_CPP11_90 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1500) +#define span_CPP11_100 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1600) +#define span_CPP11_110 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1700) +#define span_CPP11_120 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1800) +#define span_CPP11_140 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1900) + +#define span_CPP14_000 (span_CPP14_OR_GREATER) +#define span_CPP14_120 (span_CPP14_OR_GREATER || span_COMPILER_MSVC_VER >= 1800) +#define span_CPP14_140 (span_CPP14_OR_GREATER || span_COMPILER_MSVC_VER >= 1900) + +#define span_CPP17_000 (span_CPP17_OR_GREATER) + +// Presence of C++11 language features: + +#define span_HAVE_ALIAS_TEMPLATE span_CPP11_140 +#define span_HAVE_AUTO span_CPP11_100 +#define span_HAVE_CONSTEXPR_11 span_CPP11_140 +#define span_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG span_CPP11_120 +#define span_HAVE_EXPLICIT_CONVERSION span_CPP11_140 +#define span_HAVE_INITIALIZER_LIST span_CPP11_120 +#define span_HAVE_IS_DEFAULT span_CPP11_140 +#define span_HAVE_IS_DELETE span_CPP11_140 +#define span_HAVE_NOEXCEPT span_CPP11_140 +#define span_HAVE_NULLPTR span_CPP11_100 +#define span_HAVE_STATIC_ASSERT span_CPP11_100 + +// Presence of C++14 language features: + +#define span_HAVE_CONSTEXPR_14 span_CPP14_000 + +// Presence of C++17 language features: + +#define span_HAVE_DEPRECATED span_CPP17_000 +#define span_HAVE_NODISCARD span_CPP17_000 +#define span_HAVE_NORETURN span_CPP17_000 + +// MSVC: template parameter deduction guides since Visual Studio 2017 v15.7 + +#if defined(__cpp_deduction_guides) +# define span_HAVE_DEDUCTION_GUIDES 1 +#else +# define span_HAVE_DEDUCTION_GUIDES (span_CPP17_OR_GREATER && ! span_BETWEEN( span_COMPILER_MSVC_VER, 1, 1913 )) +#endif + +// Presence of C++ library features: + +#define span_HAVE_ADDRESSOF span_CPP17_000 +#define span_HAVE_ARRAY span_CPP11_110 +#define span_HAVE_BYTE span_CPP17_000 +#define span_HAVE_CONDITIONAL span_CPP11_120 +#define span_HAVE_CONTAINER_DATA_METHOD (span_CPP11_140 || ( span_COMPILER_MSVC_VER >= 1500 && span_HAS_CPP0X )) +#define span_HAVE_DATA span_CPP17_000 +#define span_HAVE_LONGLONG span_CPP11_80 +#define span_HAVE_REMOVE_CONST span_CPP11_110 +#define span_HAVE_SNPRINTF span_CPP11_140 +#define span_HAVE_STRUCT_BINDING span_CPP11_120 +#define span_HAVE_TYPE_TRAITS span_CPP11_90 + +// Presence of byte-lite: + +#ifdef NONSTD_BYTE_LITE_HPP +# define span_HAVE_NONSTD_BYTE 1 +#else +# define span_HAVE_NONSTD_BYTE 0 +#endif + +// C++ feature usage: + +#if span_HAVE_ADDRESSOF +# define span_ADDRESSOF(x) std::addressof(x) +#else +# define span_ADDRESSOF(x) (&x) +#endif + +#if span_HAVE_CONSTEXPR_11 +# define span_constexpr constexpr +#else +# define span_constexpr /*span_constexpr*/ +#endif + +#if span_HAVE_CONSTEXPR_14 +# define span_constexpr14 constexpr +#else +# define span_constexpr14 /*span_constexpr*/ +#endif + +#if span_HAVE_EXPLICIT_CONVERSION +# define span_explicit explicit +#else +# define span_explicit /*explicit*/ +#endif + +#if span_HAVE_IS_DELETE +# define span_is_delete = delete +#else +# define span_is_delete +#endif + +#if span_HAVE_IS_DELETE +# define span_is_delete_access public +#else +# define span_is_delete_access private +#endif + +#if span_HAVE_NOEXCEPT && ! span_CONFIG_CONTRACT_VIOLATION_THROWS_V +# define span_noexcept noexcept +#else +# define span_noexcept /*noexcept*/ +#endif + +#if span_HAVE_NULLPTR +# define span_nullptr nullptr +#else +# define span_nullptr NULL +#endif + +#if span_HAVE_DEPRECATED +# define span_deprecated(msg) [[deprecated(msg)]] +#else +# define span_deprecated(msg) /*[[deprecated]]*/ +#endif + +#if span_HAVE_NODISCARD +# define span_nodiscard [[nodiscard]] +#else +# define span_nodiscard /*[[nodiscard]]*/ +#endif + +#if span_HAVE_NORETURN +# define span_noreturn [[noreturn]] +#else +# define span_noreturn /*[[noreturn]]*/ +#endif + +// Other features: + +#define span_HAVE_CONSTRAINED_SPAN_CONTAINER_CTOR span_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG +#define span_HAVE_ITERATOR_CTOR span_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG + +// Additional includes: + +#if span_HAVE( ADDRESSOF ) +# include +#endif + +#if span_HAVE( ARRAY ) +# include +#endif + +#if span_HAVE( BYTE ) +# include +#endif + +#if span_HAVE( DATA ) +# include // for std::data(), std::size() +#endif + +#if span_HAVE( TYPE_TRAITS ) +# include +#endif + +#if ! span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) +# include +#endif + +#if span_FEATURE( MEMBER_AT ) > 1 +# include +#endif + +#if ! span_CONFIG( NO_EXCEPTIONS ) +# include +#endif + +// Contract violation + +#define span_ELIDE_CONTRACT_EXPECTS ( 0 == ( span_CONFIG_CONTRACT_LEVEL_MASK & 0x01 ) ) +#define span_ELIDE_CONTRACT_ENSURES ( 0 == ( span_CONFIG_CONTRACT_LEVEL_MASK & 0x10 ) ) + +#if span_ELIDE_CONTRACT_EXPECTS +# define span_constexpr_exp span_constexpr +# define span_EXPECTS( cond ) /* Expect elided */ +#else +# define span_constexpr_exp span_constexpr14 +# define span_EXPECTS( cond ) span_CONTRACT_CHECK( "Precondition", cond ) +#endif + +#if span_ELIDE_CONTRACT_ENSURES +# define span_constexpr_ens span_constexpr +# define span_ENSURES( cond ) /* Ensures elided */ +#else +# define span_constexpr_ens span_constexpr14 +# define span_ENSURES( cond ) span_CONTRACT_CHECK( "Postcondition", cond ) +#endif + +#define span_CONTRACT_CHECK( type, cond ) \ + cond ? static_cast< void >( 0 ) \ + : nonstd::span_lite::detail::report_contract_violation( span_LOCATION( __FILE__, __LINE__ ) ": " type " violation." ) + +#ifdef __GNUG__ +# define span_LOCATION( file, line ) file ":" span_STRINGIFY( line ) +#else +# define span_LOCATION( file, line ) file "(" span_STRINGIFY( line ) ")" +#endif + +// Method enabling + +#if span_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + +#define span_REQUIRES_0(VA) \ + template< bool B = (VA), typename std::enable_if::type = 0 > + +# if span_BETWEEN( span_COMPILER_MSVC_VERSION, 1, 140 ) +// VS 2013 and earlier seem to have trouble with SFINAE for default non-type arguments +# define span_REQUIRES_T(VA) \ + , typename = typename std::enable_if< ( VA ), nonstd::span_lite::detail::enabler >::type +# else +# define span_REQUIRES_T(VA) \ + , typename std::enable_if< (VA), int >::type = 0 +# endif + +#define span_REQUIRES_R(R, VA) \ + typename std::enable_if< (VA), R>::type + +#define span_REQUIRES_A(VA) \ + , typename std::enable_if< (VA), void*>::type = nullptr + +#else + +# define span_REQUIRES_0(VA) /*empty*/ +# define span_REQUIRES_T(VA) /*empty*/ +# define span_REQUIRES_R(R, VA) R +# define span_REQUIRES_A(VA) /*empty*/ + +#endif + +namespace nonstd { +namespace span_lite { + +// [views.constants], constants + +typedef span_CONFIG_EXTENT_TYPE extent_t; +typedef span_CONFIG_SIZE_TYPE size_t; + +span_constexpr const extent_t dynamic_extent = static_cast( -1 ); + +template< class T, extent_t Extent = dynamic_extent > +class span; + +// Tag to select span constructor taking a container (prevent ms-gsl warning C26426): + +struct with_container_t { span_constexpr with_container_t() span_noexcept {} }; +const span_constexpr with_container_t with_container; + +// C++11 emulation: + +namespace std11 { + +#if span_HAVE( REMOVE_CONST ) + +using std::remove_cv; +using std::remove_const; +using std::remove_volatile; + +#else + +template< class T > struct remove_const { typedef T type; }; +template< class T > struct remove_const< T const > { typedef T type; }; + +template< class T > struct remove_volatile { typedef T type; }; +template< class T > struct remove_volatile< T volatile > { typedef T type; }; + +template< class T > +struct remove_cv +{ + typedef typename std11::remove_volatile< typename std11::remove_const< T >::type >::type type; +}; + +#endif // span_HAVE( REMOVE_CONST ) + +#if span_HAVE( TYPE_TRAITS ) + +using std::is_same; +using std::is_signed; +using std::integral_constant; +using std::true_type; +using std::false_type; +using std::remove_reference; + +#else + +template< class T, T v > struct integral_constant { enum { value = v }; }; +typedef integral_constant< bool, true > true_type; +typedef integral_constant< bool, false > false_type; + +template< class T, class U > struct is_same : false_type{}; +template< class T > struct is_same : true_type{}; + +template< typename T > struct is_signed : false_type {}; +template<> struct is_signed : true_type {}; +template<> struct is_signed : true_type {}; +template<> struct is_signed : true_type {}; + +#endif + +} // namespace std11 + +// C++17 emulation: + +namespace std17 { + +template< bool v > struct bool_constant : std11::integral_constant{}; + +#if span_CPP11_120 + +template< class...> +using void_t = void; + +#endif + +#if span_HAVE( DATA ) + +using std::data; +using std::size; + +#elif span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) + +template< typename T, std::size_t N > +inline span_constexpr auto size( const T(&)[N] ) span_noexcept -> size_t +{ + return N; +} + +template< typename C > +inline span_constexpr auto size( C const & cont ) -> decltype( cont.size() ) +{ + return cont.size(); +} + +template< typename T, std::size_t N > +inline span_constexpr auto data( T(&arr)[N] ) span_noexcept -> T* +{ + return &arr[0]; +} + +template< typename C > +inline span_constexpr auto data( C & cont ) -> decltype( cont.data() ) +{ + return cont.data(); +} + +template< typename C > +inline span_constexpr auto data( C const & cont ) -> decltype( cont.data() ) +{ + return cont.data(); +} + +template< typename E > +inline span_constexpr auto data( std::initializer_list il ) span_noexcept -> E const * +{ + return il.begin(); +} + +#endif // span_HAVE( DATA ) + +#if span_HAVE( BYTE ) +using std::byte; +#elif span_HAVE( NONSTD_BYTE ) +using nonstd::byte; +#endif + +} // namespace std17 + +// C++20 emulation: + +namespace std20 { + +#if span_HAVE( DEDUCTION_GUIDES ) +template< class T > +using iter_reference_t = decltype( *std::declval() ); +#endif + +} // namespace std20 + +// Implementation details: + +namespace detail { + +/*enum*/ struct enabler{}; + +template< typename T > +bool is_positive( T x ) +{ + return std11::is_signed::value ? x >= 0 : true; +} + +#if span_HAVE( TYPE_TRAITS ) + +template< class Q > +struct is_span_oracle : std::false_type{}; + +template< class T, span_CONFIG_EXTENT_TYPE Extent > +struct is_span_oracle< span > : std::true_type{}; + +template< class Q > +struct is_span : is_span_oracle< typename std::remove_cv::type >{}; + +template< class Q > +struct is_std_array_oracle : std::false_type{}; + +#if span_HAVE( ARRAY ) + +template< class T, std::size_t Extent > +struct is_std_array_oracle< std::array > : std::true_type{}; + +#endif + +template< class Q > +struct is_std_array : is_std_array_oracle< typename std::remove_cv::type >{}; + +template< class Q > +struct is_array : std::false_type {}; + +template< class T > +struct is_array : std::true_type {}; + +template< class T, std::size_t N > +struct is_array : std::true_type {}; + +#if span_CPP11_140 && ! span_BETWEEN( span_COMPILER_GNUC_VERSION, 1, 500 ) + +template< class, class = void > +struct has_size_and_data : std::false_type{}; + +template< class C > +struct has_size_and_data +< + C, std17::void_t< + decltype( std17::size(std::declval()) ), + decltype( std17::data(std::declval()) ) > +> : std::true_type{}; + +template< class, class, class = void > +struct is_compatible_element : std::false_type {}; + +template< class C, class E > +struct is_compatible_element +< + C, E, std17::void_t< + decltype( std17::data(std::declval()) ) > +> : std::is_convertible< typename std::remove_pointer() ) )>::type(*)[], E(*)[] >{}; + +template< class C > +struct is_container : std17::bool_constant +< + ! is_span< C >::value + && ! is_array< C >::value + && ! is_std_array< C >::value + && has_size_and_data< C >::value +>{}; + +template< class C, class E > +struct is_compatible_container : std17::bool_constant +< + is_container::value + && is_compatible_element::value +>{}; + +#else // span_CPP11_140 + +template< + class C, class E + span_REQUIRES_T(( + ! is_span< C >::value + && ! is_array< C >::value + && ! is_std_array< C >::value + && ( std::is_convertible< typename std::remove_pointer() ) )>::type(*)[], E(*)[] >::value) + // && has_size_and_data< C >::value + )) + , class = decltype( std17::size(std::declval()) ) + , class = decltype( std17::data(std::declval()) ) +> +struct is_compatible_container : std::true_type{}; + +#endif // span_CPP11_140 + +#endif // span_HAVE( TYPE_TRAITS ) + +#if ! span_CONFIG( NO_EXCEPTIONS ) +#if span_FEATURE( MEMBER_AT ) > 1 + +// format index and size: + +#if defined(__clang__) +# pragma clang diagnostic ignored "-Wlong-long" +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wformat=ll" +# pragma GCC diagnostic ignored "-Wlong-long" +#endif + +inline void throw_out_of_range( size_t idx, size_t size ) +{ + const char fmt[] = "span::at(): index '%lli' is out of range [0..%lli)"; + char buffer[ 2 * 20 + sizeof fmt ]; + sprintf( buffer, fmt, static_cast(idx), static_cast(size) ); + + throw std::out_of_range( buffer ); +} + +#else // MEMBER_AT + +inline void throw_out_of_range( size_t /*idx*/, size_t /*size*/ ) +{ + throw std::out_of_range( "span::at(): index outside span" ); +} +#endif // MEMBER_AT +#endif // NO_EXCEPTIONS + +#if span_CONFIG( CONTRACT_VIOLATION_THROWS_V ) + +struct contract_violation : std::logic_error +{ + explicit contract_violation( char const * const message ) + : std::logic_error( message ) + {} +}; + +inline void report_contract_violation( char const * msg ) +{ + throw contract_violation( msg ); +} + +#else // span_CONFIG( CONTRACT_VIOLATION_THROWS_V ) + +span_noreturn inline void report_contract_violation( char const * /*msg*/ ) span_noexcept +{ + std::terminate(); +} + +#endif // span_CONFIG( CONTRACT_VIOLATION_THROWS_V ) + +} // namespace detail + +// Prevent signed-unsigned mismatch: + +#define span_sizeof(T) static_cast( sizeof(T) ) + +template< class T > +inline span_constexpr size_t to_size( T size ) +{ + return static_cast( size ); +} + +// +// [views.span] - A view over a contiguous, single-dimension sequence of objects +// +template< class T, extent_t Extent /*= dynamic_extent*/ > +class span +{ +public: + // constants and types + + typedef T element_type; + typedef typename std11::remove_cv< T >::type value_type; + + typedef T & reference; + typedef T * pointer; + typedef T const * const_pointer; + typedef T const & const_reference; + + typedef size_t size_type; + typedef extent_t extent_type; + + typedef pointer iterator; + typedef const_pointer const_iterator; + + typedef std::ptrdiff_t difference_type; + + typedef std::reverse_iterator< iterator > reverse_iterator; + typedef std::reverse_iterator< const_iterator > const_reverse_iterator; + +// static constexpr extent_type extent = Extent; + enum { extent = Extent }; + + // 26.7.3.2 Constructors, copy, and assignment [span.cons] + + span_REQUIRES_0( + ( Extent == 0 ) || + ( Extent == dynamic_extent ) + ) + span_constexpr span() span_noexcept + : data_( span_nullptr ) + , size_( 0 ) + { + // span_EXPECTS( data() == span_nullptr ); + // span_EXPECTS( size() == 0 ); + } + +#if span_HAVE( ITERATOR_CTOR ) + // Didn't yet succeed in combining the next two constructors: + + span_constexpr_exp span( std::nullptr_t, size_type count ) + : data_( span_nullptr ) + , size_( count ) + { + span_EXPECTS( data_ == span_nullptr && count == 0 ); + } + + template< typename It + span_REQUIRES_T(( + std::is_convertible()), element_type>::value + )) + > + span_constexpr_exp span( It first, size_type count ) + : data_( to_address( first ) ) + , size_( count ) + { + span_EXPECTS( + ( data_ == span_nullptr && count == 0 ) || + ( data_ != span_nullptr && detail::is_positive( count ) ) + ); + } +#else + span_constexpr_exp span( pointer ptr, size_type count ) + : data_( ptr ) + , size_( count ) + { + span_EXPECTS( + ( ptr == span_nullptr && count == 0 ) || + ( ptr != span_nullptr && detail::is_positive( count ) ) + ); + } +#endif + +#if span_HAVE( ITERATOR_CTOR ) + template< typename It, typename End + span_REQUIRES_T(( + std::is_convertible()), element_type>::value + && ! std::is_convertible::value + )) + > + span_constexpr_exp span( It first, End last ) + : data_( to_address( first ) ) + , size_( to_size( last - first ) ) + { + span_EXPECTS( + last - first >= 0 + ); + } +#else + span_constexpr_exp span( pointer first, pointer last ) + : data_( first ) + , size_( to_size( last - first ) ) + { + span_EXPECTS( + last - first >= 0 + ); + } +#endif + + template< std::size_t N + span_REQUIRES_T(( + (Extent == dynamic_extent || Extent == static_cast(N)) + && std::is_convertible< value_type(*)[], element_type(*)[] >::value + )) + > + span_constexpr span( element_type ( &arr )[ N ] ) span_noexcept + : data_( span_ADDRESSOF( arr[0] ) ) + , size_( N ) + {} + +#if span_HAVE( ARRAY ) + + template< std::size_t N + span_REQUIRES_T(( + (Extent == dynamic_extent || Extent == static_cast(N)) + && std::is_convertible< value_type(*)[], element_type(*)[] >::value + )) + > +# if span_FEATURE( CONSTRUCTION_FROM_STDARRAY_ELEMENT_TYPE ) + span_constexpr span( std::array< element_type, N > & arr ) span_noexcept +# else + span_constexpr span( std::array< value_type, N > & arr ) span_noexcept +# endif + : data_( arr.data() ) + , size_( to_size( arr.size() ) ) + {} + + template< std::size_t N +# if span_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + span_REQUIRES_T(( + (Extent == dynamic_extent || Extent == static_cast(N)) + && std::is_convertible< value_type(*)[], element_type(*)[] >::value + )) +# endif + > + span_constexpr span( std::array< value_type, N> const & arr ) span_noexcept + : data_( arr.data() ) + , size_( to_size( arr.size() ) ) + {} + +#endif // span_HAVE( ARRAY ) + +#if span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) + template< class Container + span_REQUIRES_T(( + detail::is_compatible_container< Container, element_type >::value + )) + > + span_constexpr span( Container & cont ) + : data_( std17::data( cont ) ) + , size_( to_size( std17::size( cont ) ) ) + {} + + template< class Container + span_REQUIRES_T(( + std::is_const< element_type >::value + && detail::is_compatible_container< Container, element_type >::value + )) + > + span_constexpr span( Container const & cont ) + : data_( std17::data( cont ) ) + , size_( to_size( std17::size( cont ) ) ) + {} + +#endif // span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) + +#if span_FEATURE( WITH_CONTAINER ) + + template< class Container > + span_constexpr span( with_container_t, Container & cont ) + : data_( cont.size() == 0 ? span_nullptr : span_ADDRESSOF( cont[0] ) ) + , size_( to_size( cont.size() ) ) + {} + + template< class Container > + span_constexpr span( with_container_t, Container const & cont ) + : data_( cont.size() == 0 ? span_nullptr : const_cast( span_ADDRESSOF( cont[0] ) ) ) + , size_( to_size( cont.size() ) ) + {} +#endif + +#if span_HAVE( IS_DEFAULT ) + span_constexpr span( span const & other ) span_noexcept = default; + + ~span() span_noexcept = default; + + span_constexpr14 span & operator=( span const & other ) span_noexcept = default; +#else + span_constexpr span( span const & other ) span_noexcept + : data_( other.data_ ) + , size_( other.size_ ) + {} + + ~span() span_noexcept + {} + + span_constexpr14 span & operator=( span const & other ) span_noexcept + { + data_ = other.data_; + size_ = other.size_; + + return *this; + } +#endif + + template< class OtherElementType, extent_type OtherExtent + span_REQUIRES_T(( + (Extent == dynamic_extent || Extent == OtherExtent) + && std::is_convertible::value + )) + > + span_constexpr_exp span( span const & other ) span_noexcept + : data_( reinterpret_cast( other.data() ) ) + , size_( other.size() ) + { + span_EXPECTS( OtherExtent == dynamic_extent || other.size() == to_size(OtherExtent) ); + } + + // 26.7.3.3 Subviews [span.sub] + + template< extent_type Count > + span_constexpr_exp span< element_type, Count > + first() const + { + span_EXPECTS( detail::is_positive( Count ) && Count <= size() ); + + return span< element_type, Count >( data(), Count ); + } + + template< extent_type Count > + span_constexpr_exp span< element_type, Count > + last() const + { + span_EXPECTS( detail::is_positive( Count ) && Count <= size() ); + + return span< element_type, Count >( data() + (size() - Count), Count ); + } + +#if span_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + template< size_type Offset, extent_type Count = dynamic_extent > +#else + template< size_type Offset, extent_type Count /*= dynamic_extent*/ > +#endif + span_constexpr_exp span< element_type, Count > + subspan() const + { + span_EXPECTS( + ( detail::is_positive( Offset ) && Offset <= size() ) && + ( Count == dynamic_extent || (detail::is_positive( Count ) && Count + Offset <= size()) ) + ); + + return span< element_type, Count >( + data() + Offset, Count != dynamic_extent ? Count : (Extent != dynamic_extent ? Extent - Offset : size() - Offset) ); + } + + span_constexpr_exp span< element_type, dynamic_extent > + first( size_type count ) const + { + span_EXPECTS( detail::is_positive( count ) && count <= size() ); + + return span< element_type, dynamic_extent >( data(), count ); + } + + span_constexpr_exp span< element_type, dynamic_extent > + last( size_type count ) const + { + span_EXPECTS( detail::is_positive( count ) && count <= size() ); + + return span< element_type, dynamic_extent >( data() + ( size() - count ), count ); + } + + span_constexpr_exp span< element_type, dynamic_extent > + subspan( size_type offset, size_type count = static_cast(dynamic_extent) ) const + { + span_EXPECTS( + ( ( detail::is_positive( offset ) && offset <= size() ) ) && + ( count == static_cast(dynamic_extent) || ( detail::is_positive( count ) && offset + count <= size() ) ) + ); + + return span< element_type, dynamic_extent >( + data() + offset, count == static_cast(dynamic_extent) ? size() - offset : count ); + } + + // 26.7.3.4 Observers [span.obs] + + span_constexpr size_type size() const span_noexcept + { + return size_; + } + + span_constexpr std::ptrdiff_t ssize() const span_noexcept + { + return static_cast( size_ ); + } + + span_constexpr size_type size_bytes() const span_noexcept + { + return size() * to_size( sizeof( element_type ) ); + } + + span_nodiscard span_constexpr bool empty() const span_noexcept + { + return size() == 0; + } + + // 26.7.3.5 Element access [span.elem] + + span_constexpr_exp reference operator[]( size_type idx ) const + { + span_EXPECTS( detail::is_positive( idx ) && idx < size() ); + + return *( data() + idx ); + } + +#if span_FEATURE( MEMBER_CALL_OPERATOR ) + span_deprecated("replace operator() with operator[]") + + span_constexpr_exp reference operator()( size_type idx ) const + { + span_EXPECTS( detail::is_positive( idx ) && idx < size() ); + + return *( data() + idx ); + } +#endif + +#if span_FEATURE( MEMBER_AT ) + span_constexpr14 reference at( size_type idx ) const + { +#if span_CONFIG( NO_EXCEPTIONS ) + return this->operator[]( idx ); +#else + if ( !detail::is_positive( idx ) || size() <= idx ) + { + detail::throw_out_of_range( idx, size() ); + } + return *( data() + idx ); +#endif + } +#endif + + span_constexpr pointer data() const span_noexcept + { + return data_; + } + +#if span_FEATURE( MEMBER_BACK_FRONT ) + + span_constexpr_exp reference front() const span_noexcept + { + span_EXPECTS( ! empty() ); + + return *data(); + } + + span_constexpr_exp reference back() const span_noexcept + { + span_EXPECTS( ! empty() ); + + return *( data() + size() - 1 ); + } + +#endif + + // xx.x.x.x Modifiers [span.modifiers] + +#if span_FEATURE( MEMBER_SWAP ) + + span_constexpr14 void swap( span & other ) span_noexcept + { + using std::swap; + swap( data_, other.data_ ); + swap( size_, other.size_ ); + } +#endif + + // 26.7.3.6 Iterator support [span.iterators] + + span_constexpr iterator begin() const span_noexcept + { +#if span_CPP11_OR_GREATER + return { data() }; +#else + return iterator( data() ); +#endif + } + + span_constexpr iterator end() const span_noexcept + { +#if span_CPP11_OR_GREATER + return { data() + size() }; +#else + return iterator( data() + size() ); +#endif + } + + span_constexpr const_iterator cbegin() const span_noexcept + { +#if span_CPP11_OR_GREATER + return { data() }; +#else + return const_iterator( data() ); +#endif + } + + span_constexpr const_iterator cend() const span_noexcept + { +#if span_CPP11_OR_GREATER + return { data() + size() }; +#else + return const_iterator( data() + size() ); +#endif + } + + span_constexpr reverse_iterator rbegin() const span_noexcept + { + return reverse_iterator( end() ); + } + + span_constexpr reverse_iterator rend() const span_noexcept + { + return reverse_iterator( begin() ); + } + + span_constexpr const_reverse_iterator crbegin() const span_noexcept + { + return const_reverse_iterator ( cend() ); + } + + span_constexpr const_reverse_iterator crend() const span_noexcept + { + return const_reverse_iterator( cbegin() ); + } + +private: + + // Note: C++20 has std::pointer_traits::to_address( it ); + +#if span_HAVE( ITERATOR_CTOR ) + static inline span_constexpr pointer to_address( std::nullptr_t ) span_noexcept + { + return nullptr; + } + + template< typename U > + static inline span_constexpr U * to_address( U * p ) span_noexcept + { + return p; + } + + template< typename Ptr + span_REQUIRES_T(( ! std::is_pointer::value )) + > + static inline span_constexpr pointer to_address( Ptr const & it ) span_noexcept + { + return to_address( it.operator->() ); + } +#endif // span_HAVE( ITERATOR_CTOR ) + +private: + pointer data_; + size_type size_; +}; + +// class template argument deduction guides: + +#if span_HAVE( DEDUCTION_GUIDES ) + +template< class T, size_t N > +span( T (&)[N] ) -> span(N)>; + +template< class T, size_t N > +span( std::array & ) -> span(N)>; + +template< class T, size_t N > +span( std::array const & ) -> span(N)>; + +#if span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) + +template< class Container > +span( Container& ) -> span; + +template< class Container > +span( Container const & ) -> span; + +#endif + +// iterator: constraints: It satisfies contiguous_­iterator. + +template< class It, class EndOrSize > +span( It, EndOrSize ) -> span< typename std11::remove_reference< typename std20::iter_reference_t >::type >; + +#endif // span_HAVE( DEDUCTION_GUIDES ) + +// 26.7.3.7 Comparison operators [span.comparison] + +#if span_FEATURE( COMPARISON ) +#if span_FEATURE( SAME ) + +template< class T1, extent_t E1, class T2, extent_t E2 > +inline span_constexpr bool same( span const & l, span const & r ) span_noexcept +{ + return std11::is_same::value + && l.size() == r.size() + && static_cast( l.data() ) == r.data(); +} + +#endif + +template< class T1, extent_t E1, class T2, extent_t E2 > +inline span_constexpr bool operator==( span const & l, span const & r ) +{ + return +#if span_FEATURE( SAME ) + same( l, r ) || +#endif + ( l.size() == r.size() && std::equal( l.begin(), l.end(), r.begin() ) ); +} + +template< class T1, extent_t E1, class T2, extent_t E2 > +inline span_constexpr bool operator<( span const & l, span const & r ) +{ + return std::lexicographical_compare( l.begin(), l.end(), r.begin(), r.end() ); +} + +template< class T1, extent_t E1, class T2, extent_t E2 > +inline span_constexpr bool operator!=( span const & l, span const & r ) +{ + return !( l == r ); +} + +template< class T1, extent_t E1, class T2, extent_t E2 > +inline span_constexpr bool operator<=( span const & l, span const & r ) +{ + return !( r < l ); +} + +template< class T1, extent_t E1, class T2, extent_t E2 > +inline span_constexpr bool operator>( span const & l, span const & r ) +{ + return ( r < l ); +} + +template< class T1, extent_t E1, class T2, extent_t E2 > +inline span_constexpr bool operator>=( span const & l, span const & r ) +{ + return !( l < r ); +} + +#endif // span_FEATURE( COMPARISON ) + +// 26.7.2.6 views of object representation [span.objectrep] + +#if span_HAVE( BYTE ) || span_HAVE( NONSTD_BYTE ) + +// Avoid MSVC 14.1 (1910), VS 2017: warning C4307: '*': integral constant overflow: + +template< typename T, extent_t Extent > +struct BytesExtent +{ +#if span_CPP11_OR_GREATER + enum ET : extent_t { value = span_sizeof(T) * Extent }; +#else + enum ET { value = span_sizeof(T) * Extent }; +#endif +}; + +template< typename T > +struct BytesExtent< T, dynamic_extent > +{ +#if span_CPP11_OR_GREATER + enum ET : extent_t { value = dynamic_extent }; +#else + enum ET { value = dynamic_extent }; +#endif +}; + +template< class T, extent_t Extent > +inline span_constexpr span< const std17::byte, BytesExtent::value > +as_bytes( span spn ) span_noexcept +{ +#if 0 + return { reinterpret_cast< std17::byte const * >( spn.data() ), spn.size_bytes() }; +#else + return span< const std17::byte, BytesExtent::value >( + reinterpret_cast< std17::byte const * >( spn.data() ), spn.size_bytes() ); // NOLINT +#endif +} + +template< class T, extent_t Extent > +inline span_constexpr span< std17::byte, BytesExtent::value > +as_writable_bytes( span spn ) span_noexcept +{ +#if 0 + return { reinterpret_cast< std17::byte * >( spn.data() ), spn.size_bytes() }; +#else + return span< std17::byte, BytesExtent::value >( + reinterpret_cast< std17::byte * >( spn.data() ), spn.size_bytes() ); // NOLINT +#endif +} + +#endif // span_HAVE( BYTE ) || span_HAVE( NONSTD_BYTE ) + +// extensions: non-member views: +// this feature implies the presence of make_span() + +#if span_FEATURE( NON_MEMBER_FIRST_LAST_SUB ) && span_CPP11_120 + +template< extent_t Count, class T > +span_constexpr auto +first( T & t ) -> decltype( make_span(t).template first() ) +{ + return make_span( t ).template first(); +} + +template< class T > +span_constexpr auto +first( T & t, size_t count ) -> decltype( make_span(t).first(count) ) +{ + return make_span( t ).first( count ); +} + +template< extent_t Count, class T > +span_constexpr auto +last( T & t ) -> decltype( make_span(t).template last() ) +{ + return make_span(t).template last(); +} + +template< class T > +span_constexpr auto +last( T & t, extent_t count ) -> decltype( make_span(t).last(count) ) +{ + return make_span( t ).last( count ); +} + +template< size_t Offset, extent_t Count = dynamic_extent, class T > +span_constexpr auto +subspan( T & t ) -> decltype( make_span(t).template subspan() ) +{ + return make_span( t ).template subspan(); +} + +template< class T > +span_constexpr auto +subspan( T & t, size_t offset, extent_t count = dynamic_extent ) -> decltype( make_span(t).subspan(offset, count) ) +{ + return make_span( t ).subspan( offset, count ); +} + +#endif // span_FEATURE( NON_MEMBER_FIRST_LAST_SUB ) + +// 27.8 Container and view access [iterator.container] + +template< class T, extent_t Extent /*= dynamic_extent*/ > +span_constexpr std::size_t size( span const & spn ) +{ + return static_cast( spn.size() ); +} + +template< class T, extent_t Extent /*= dynamic_extent*/ > +span_constexpr std::ptrdiff_t ssize( span const & spn ) +{ + return static_cast( spn.size() ); +} + +} // namespace span_lite +} // namespace nonstd + +// make available in nonstd: + +namespace nonstd { + +using span_lite::dynamic_extent; + +using span_lite::span; + +using span_lite::with_container; + +#if span_FEATURE( COMPARISON ) +#if span_FEATURE( SAME ) +using span_lite::same; +#endif + +using span_lite::operator==; +using span_lite::operator!=; +using span_lite::operator<; +using span_lite::operator<=; +using span_lite::operator>; +using span_lite::operator>=; +#endif + +#if span_HAVE( BYTE ) +using span_lite::as_bytes; +using span_lite::as_writable_bytes; +#endif + +using span_lite::size; +using span_lite::ssize; + +} // namespace nonstd + +#endif // span_USES_STD_SPAN + +// make_span() [span-lite extension]: + +#if span_FEATURE( MAKE_SPAN ) || span_FEATURE( NON_MEMBER_FIRST_LAST_SUB ) + +#if span_USES_STD_SPAN +# define span_constexpr constexpr +# define span_noexcept noexcept +# define span_nullptr nullptr +# ifndef span_CONFIG_EXTENT_TYPE +# define span_CONFIG_EXTENT_TYPE std::size_t +# endif +using extent_t = span_CONFIG_EXTENT_TYPE; +#endif // span_USES_STD_SPAN + +namespace nonstd { +namespace span_lite { + +template< class T > +inline span_constexpr span +make_span( T * ptr, size_t count ) span_noexcept +{ + return span( ptr, count ); +} + +template< class T > +inline span_constexpr span +make_span( T * first, T * last ) span_noexcept +{ + return span( first, last ); +} + +template< class T, std::size_t N > +inline span_constexpr span(N)> +make_span( T ( &arr )[ N ] ) span_noexcept +{ + return span(N)>( &arr[ 0 ], N ); +} + +#if span_USES_STD_SPAN || span_HAVE( ARRAY ) + +template< class T, std::size_t N > +inline span_constexpr span(N)> +make_span( std::array< T, N > & arr ) span_noexcept +{ + return span(N)>( arr ); +} + +template< class T, std::size_t N > +inline span_constexpr span< const T, static_cast(N) > +make_span( std::array< T, N > const & arr ) span_noexcept +{ + return span(N)>( arr ); +} + +#endif // span_HAVE( ARRAY ) + +#if span_USES_STD_SPAN + +template< class Container, class EP = decltype( std::data(std::declval())) > +inline span_constexpr auto +make_span( Container & cont ) span_noexcept -> span< typename std::remove_pointer::type > +{ + return span< typename std::remove_pointer::type >( cont ); +} + +template< class Container, class EP = decltype( std::data(std::declval())) > +inline span_constexpr auto +make_span( Container const & cont ) span_noexcept -> span< const typename std::remove_pointer::type > +{ + return span< const typename std::remove_pointer::type >( cont ); +} + +#elif span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) && span_HAVE( AUTO ) + +template< class Container, class EP = decltype( std17::data(std::declval())) > +inline span_constexpr auto +make_span( Container & cont ) span_noexcept -> span< typename std::remove_pointer::type > +{ + return span< typename std::remove_pointer::type >( cont ); +} + +template< class Container, class EP = decltype( std17::data(std::declval())) > +inline span_constexpr auto +make_span( Container const & cont ) span_noexcept -> span< const typename std::remove_pointer::type > +{ + return span< const typename std::remove_pointer::type >( cont ); +} + +#else + +template< class T > +inline span_constexpr span +make_span( span spn ) span_noexcept +{ + return spn; +} + +template< class T, class Allocator > +inline span_constexpr span +make_span( std::vector & cont ) span_noexcept +{ + return span( with_container, cont ); +} + +template< class T, class Allocator > +inline span_constexpr span +make_span( std::vector const & cont ) span_noexcept +{ + return span( with_container, cont ); +} + +#endif // span_USES_STD_SPAN || ( ... ) + +#if ! span_USES_STD_SPAN && span_FEATURE( WITH_CONTAINER ) + +template< class Container > +inline span_constexpr span +make_span( with_container_t, Container & cont ) span_noexcept +{ + return span< typename Container::value_type >( with_container, cont ); +} + +template< class Container > +inline span_constexpr span +make_span( with_container_t, Container const & cont ) span_noexcept +{ + return span< const typename Container::value_type >( with_container, cont ); +} + +#endif // ! span_USES_STD_SPAN && span_FEATURE( WITH_CONTAINER ) + + +} // namespace span_lite +} // namespace nonstd + +// make available in nonstd: + +namespace nonstd { +using span_lite::make_span; +} // namespace nonstd + +#endif // #if span_FEATURE_TO_STD( MAKE_SPAN ) + +#if span_CPP11_OR_GREATER && span_FEATURE( BYTE_SPAN ) && ( span_HAVE( BYTE ) || span_HAVE( NONSTD_BYTE ) ) + +namespace nonstd { +namespace span_lite { + +template< class T > +inline span_constexpr auto +byte_span( T & t ) span_noexcept -> span< std17::byte, span_sizeof(T) > +{ + return span< std17::byte, span_sizeof(t) >( reinterpret_cast< std17::byte * >( &t ), span_sizeof(T) ); +} + +template< class T > +inline span_constexpr auto +byte_span( T const & t ) span_noexcept -> span< const std17::byte, span_sizeof(T) > +{ + return span< const std17::byte, span_sizeof(t) >( reinterpret_cast< std17::byte const * >( &t ), span_sizeof(T) ); +} + +} // namespace span_lite +} // namespace nonstd + +// make available in nonstd: + +namespace nonstd { +using span_lite::byte_span; +} // namespace nonstd + +#endif // span_FEATURE( BYTE_SPAN ) + +#if span_HAVE( STRUCT_BINDING ) + +#if span_CPP14_OR_GREATER +# include +#elif span_CPP11_OR_GREATER +# include +namespace std { + template< std::size_t I, typename T > + using tuple_element_t = typename tuple_element::type; +} +#else +namespace std { + template< typename T > + class tuple_size; /*undefined*/ + + template< std::size_t I, typename T > + class tuple_element; /* undefined */ +} +#endif // span_CPP14_OR_GREATER + +namespace std { + +// 26.7.X Tuple interface + +// std::tuple_size<>: + +template< typename ElementType, nonstd::span_lite::extent_t Extent > +class tuple_size< nonstd::span > : public integral_constant(Extent)> {}; + +// std::tuple_size<>: Leave undefined for dynamic extent: + +template< typename ElementType > +class tuple_size< nonstd::span >; + +// std::tuple_element<>: + +template< size_t I, typename ElementType, nonstd::span_lite::extent_t Extent > +class tuple_element< I, nonstd::span > +{ +public: +#if span_HAVE( STATIC_ASSERT ) + static_assert( Extent != nonstd::dynamic_extent && I < Extent, "tuple_element: dynamic extent or index out of range" ); +#endif + using type = ElementType; +}; + +// std::get<>(), 2 variants: + +template< size_t I, typename ElementType, nonstd::span_lite::extent_t Extent > +span_constexpr ElementType & get( nonstd::span & spn ) span_noexcept +{ +#if span_HAVE( STATIC_ASSERT ) + static_assert( Extent != nonstd::dynamic_extent && I < Extent, "get<>(span): dynamic extent or index out of range" ); +#endif + return spn[I]; +} + +template< size_t I, typename ElementType, nonstd::span_lite::extent_t Extent > +span_constexpr ElementType const & get( nonstd::span const & spn ) span_noexcept +{ +#if span_HAVE( STATIC_ASSERT ) + static_assert( Extent != nonstd::dynamic_extent && I < Extent, "get<>(span): dynamic extent or index out of range" ); +#endif + return spn[I]; +} + +} // end namespace std + +#endif // span_HAVE( STRUCT_BINDING ) + +#if ! span_USES_STD_SPAN +span_RESTORE_WARNINGS() +#endif // span_USES_STD_SPAN + +#endif // NONSTD_SPAN_HPP_INCLUDED diff --git a/distros/ubuntu1604/NEWS b/distros/beowulf/NEWS similarity index 100% rename from distros/ubuntu1604/NEWS rename to distros/beowulf/NEWS diff --git a/distros/ubuntu1604/README.Debian b/distros/beowulf/README.Debian similarity index 100% rename from distros/ubuntu1604/README.Debian rename to distros/beowulf/README.Debian diff --git a/distros/ubuntu1204/TODO.Debian b/distros/beowulf/TODO.Debian similarity index 100% rename from distros/ubuntu1204/TODO.Debian rename to distros/beowulf/TODO.Debian diff --git a/distros/ubuntu1604/changelog b/distros/beowulf/changelog similarity index 100% rename from distros/ubuntu1604/changelog rename to distros/beowulf/changelog diff --git a/distros/ubuntu1204/clean b/distros/beowulf/clean similarity index 100% rename from distros/ubuntu1204/clean rename to distros/beowulf/clean diff --git a/distros/ubuntu1204/compat b/distros/beowulf/compat similarity index 100% rename from distros/ubuntu1204/compat rename to distros/beowulf/compat diff --git a/distros/ubuntu1604/conf/apache2/zoneminder.conf b/distros/beowulf/conf/apache2/zoneminder.conf similarity index 99% rename from distros/ubuntu1604/conf/apache2/zoneminder.conf rename to distros/beowulf/conf/apache2/zoneminder.conf index 598996bc0..e3164d36c 100644 --- a/distros/ubuntu1604/conf/apache2/zoneminder.conf +++ b/distros/beowulf/conf/apache2/zoneminder.conf @@ -6,6 +6,7 @@ ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin" Require all granted + # Order matters. This alias must come first. Alias /zm/cache /var/cache/zoneminder/cache diff --git a/distros/ubuntu1604/control b/distros/beowulf/control similarity index 84% rename from distros/ubuntu1604/control rename to distros/beowulf/control index 1b7b96320..50d25a0c4 100644 --- a/distros/ubuntu1604/control +++ b/distros/beowulf/control @@ -3,26 +3,22 @@ Section: net Priority: optional Maintainer: Isaac Connor Uploaders: Isaac Connor -Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apache2-dev, dh-linktree, dh-systemd, dh-apache2 +Build-Depends: debhelper, sphinx-doc, dh-linktree, dh-apache2 ,cmake - ,libx264-dev, libmp4v2-dev - ,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 + ,libavcodec-dev + ,libavformat-dev + ,libavutil-dev + ,libswresample-dev + ,libswscale-dev + ,ffmpeg ,net-tools ,libbz2-dev - ,libgcrypt-dev | libgcrypt11-dev ,libcurl4-gnutls-dev - ,libgnutls-openssl-dev - ,libjpeg8-dev | libjpeg9-dev | libjpeg62-turbo-dev + ,libturbojpeg0-dev ,default-libmysqlclient-dev | libmysqlclient-dev | libmariadbclient-dev-compat ,libpcre3-dev ,libpolkit-gobject-1-dev - ,libv4l-dev (>= 0.8.3) [!hurd-any] + ,libv4l-dev [!hurd-any] ,libvlc-dev ,libdate-manip-perl ,libdbd-mysql-perl @@ -35,7 +31,6 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa ,libdata-entropy-perl # Unbundled (dh_linktree): ,libjs-jquery - ,libjs-mootools Standards-Version: 3.9.8 Homepage: http://www.zoneminder.com/ Vcs-Browser: http://anonscm.debian.org/cgit/collab-maint/zoneminder.git @@ -45,10 +40,9 @@ Package: zoneminder Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,javascript-common - ,libmp4v2-2, libx264-142|libx264-148|libx264-152|libx264-155 - ,libswscale-ffmpeg3|libswscale4|libswscale3|libswscale5 - ,libswresample2|libswresample3|libswresample24|libswresample-ffmpeg1 - ,ffmpeg | libav-tools + ,libswscale5 + ,libswresample3 + ,ffmpeg ,libdate-manip-perl, libmime-lite-perl, libmime-tools-perl ,libdbd-mysql-perl ,libphp-serialization-perl @@ -61,7 +55,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libjson-maybexs-perl ,libsys-mmap-perl [!hurd-any] ,liburi-encode-perl - ,libwww-perl + ,libwww-perl, liburi-perl ,libdata-dump-perl ,libdatetime-perl ,libclass-std-fast-perl @@ -74,16 +68,15 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,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 + ,php-mysql, php-gd, php-apcu, php-apc | php-apcu-bc, php-json ,policykit-1 ,rsyslog | system-log-daemon ,zip ,libpcre3 - ,libssl | libssl1.0.0 | libssl1.1 ,libcrypt-eksblowfish-perl ,libdata-entropy-perl Recommends: ${misc:Recommends} - ,libapache2-mod-php5 | libapache2-mod-php | php5-fpm | php-fpm + ,libapache2-mod-php | php-fpm ,mysql-server | mariadb-server | virtual-mysql-server ,zoneminder-doc (>= ${source:Version}) ,ffmpeg diff --git a/distros/ubuntu1204/copyright b/distros/beowulf/copyright similarity index 96% rename from distros/ubuntu1204/copyright rename to distros/beowulf/copyright index c48025a25..f9a69959e 100644 --- a/distros/ubuntu1204/copyright +++ b/distros/beowulf/copyright @@ -9,7 +9,6 @@ Comment: on Fri, 8 Dec 2006 10:19:43 +1100 Files-Excluded: web/skins/*/js/jquery-* - web/tools/mootools/*-yc.js Files: * Copyright: 2001-2014 Philip Coombes @@ -37,17 +36,11 @@ Comment: Includes Sizzle.js http://sizzlejs.com/ Released under the MIT, BSD, and GPL Licenses. -Files: web/tools/mootools/*.js -Copyright: 2009 Marcelo Jorge Vieira (metal) - 2006-2010 Valerio Proietti (http://mad4milk.net/) -License: Expat - Files: web/api/* Copyright: 2005-2013 Cake Software Foundation, Inc. (http://cakefoundation.org) License: Expat Files: - cmake/Modules/CheckPrototypeDefinition*.cmake cmake/Modules/FindGLIB2.cmake cmake/Modules/FindPolkit.cmake cmake/Modules/GNUInstallDirs.cmake diff --git a/distros/ubuntu1204/examples/nginx.conf b/distros/beowulf/examples/nginx.conf similarity index 100% rename from distros/ubuntu1204/examples/nginx.conf rename to distros/beowulf/examples/nginx.conf diff --git a/distros/ubuntu1204/gbp.conf b/distros/beowulf/gbp.conf similarity index 100% rename from distros/ubuntu1204/gbp.conf rename to distros/beowulf/gbp.conf diff --git a/distros/ubuntu1204/libzoneminder-perl.install b/distros/beowulf/libzoneminder-perl.install similarity index 100% rename from distros/ubuntu1204/libzoneminder-perl.install rename to distros/beowulf/libzoneminder-perl.install diff --git a/distros/ubuntu1204/patches/series b/distros/beowulf/patches/series similarity index 100% rename from distros/ubuntu1204/patches/series rename to distros/beowulf/patches/series diff --git a/distros/ubuntu1604/rules b/distros/beowulf/rules similarity index 89% rename from distros/ubuntu1604/rules rename to distros/beowulf/rules index c671a1b03..e26f1bb91 100755 --- a/distros/ubuntu1604/rules +++ b/distros/beowulf/rules @@ -13,7 +13,7 @@ endif %: dh $@ --parallel --buildsystem=cmake --builddirectory=dbuild \ - --with systemd,sphinxdoc,apache2,linktree + --with sphinxdoc,apache2,linktree override_dh_auto_configure: dh_auto_configure -- $(ARGS) \ @@ -21,8 +21,8 @@ override_dh_auto_configure: -DCMAKE_BUILD_TYPE=Release \ -DZM_CONFIG_DIR="/etc/zm" \ -DZM_CONFIG_SUBDIR="/etc/zm/conf.d" \ - -DZM_RUNDIR="/var/run/zm" \ - -DZM_SOCKDIR="/var/run/zm" \ + -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" \ @@ -67,15 +67,6 @@ override_dh_fixperms: chown root:www-data $(CURDIR)/debian/zoneminder/etc/zm/zm.conf chmod 640 $(CURDIR)/debian/zoneminder/etc/zm/zm.conf -override_dh_systemd_start: - dh_systemd_start --no-start - -override_dh_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 \ diff --git a/distros/ubuntu1204/source/format b/distros/beowulf/source/format similarity index 100% rename from distros/ubuntu1204/source/format rename to distros/beowulf/source/format diff --git a/distros/beowulf/source/lintian-overrides b/distros/beowulf/source/lintian-overrides new file mode 100644 index 000000000..f905a5a2f --- /dev/null +++ b/distros/beowulf/source/lintian-overrides @@ -0,0 +1,5 @@ +## 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/ubuntu1204/zoneminder-doc.doc-base b/distros/beowulf/zoneminder-doc.doc-base similarity index 100% rename from distros/ubuntu1204/zoneminder-doc.doc-base rename to distros/beowulf/zoneminder-doc.doc-base diff --git a/distros/ubuntu1204/zoneminder-doc.install b/distros/beowulf/zoneminder-doc.install similarity index 100% rename from distros/ubuntu1204/zoneminder-doc.install rename to distros/beowulf/zoneminder-doc.install diff --git a/distros/ubuntu1204/zoneminder-doc.links b/distros/beowulf/zoneminder-doc.links similarity index 100% rename from distros/ubuntu1204/zoneminder-doc.links rename to distros/beowulf/zoneminder-doc.links diff --git a/distros/ubuntu1204/zoneminder.apache2 b/distros/beowulf/zoneminder.apache2 similarity index 100% rename from distros/ubuntu1204/zoneminder.apache2 rename to distros/beowulf/zoneminder.apache2 diff --git a/distros/ubuntu1204/zoneminder.bug-presubj b/distros/beowulf/zoneminder.bug-presubj similarity index 100% rename from distros/ubuntu1204/zoneminder.bug-presubj rename to distros/beowulf/zoneminder.bug-presubj diff --git a/distros/ubuntu1604/zoneminder.dirs b/distros/beowulf/zoneminder.dirs similarity index 100% rename from distros/ubuntu1604/zoneminder.dirs rename to distros/beowulf/zoneminder.dirs diff --git a/distros/ubuntu1204/zoneminder.docs b/distros/beowulf/zoneminder.docs similarity index 100% rename from distros/ubuntu1204/zoneminder.docs rename to distros/beowulf/zoneminder.docs diff --git a/distros/ubuntu1204/zoneminder.examples b/distros/beowulf/zoneminder.examples similarity index 100% rename from distros/ubuntu1204/zoneminder.examples rename to distros/beowulf/zoneminder.examples diff --git a/distros/ubuntu1204/zoneminder.init b/distros/beowulf/zoneminder.init similarity index 86% rename from distros/ubuntu1204/zoneminder.init rename to distros/beowulf/zoneminder.init index de552848b..6132481f3 100644 --- a/distros/ubuntu1204/zoneminder.init +++ b/distros/beowulf/zoneminder.init @@ -17,19 +17,12 @@ prog=ZoneMinder ZM_PATH_BIN="/usr/bin" -RUNDIR="/var/run/zm" +RUNDIR="/run/zm" TMPDIR="/tmp/zm" command="$ZM_PATH_BIN/zmpkg.pl" start() { echo -n "Starting $prog: " - # Wait for mysqld to start. Continue if it takes too long. - count=0 - while [ ! -e /var/run/mysqld/mysqld.sock ] && [ $count -lt 60 ] - do - sleep 1 - count=$((count+1)) - done export TZ=:/etc/localtime mkdir -p "$RUNDIR" && chown www-data:www-data "$RUNDIR" mkdir -p "$TMPDIR" && chown www-data:www-data "$TMPDIR" diff --git a/distros/ubuntu1204/zoneminder.install b/distros/beowulf/zoneminder.install similarity index 100% rename from distros/ubuntu1204/zoneminder.install rename to distros/beowulf/zoneminder.install diff --git a/distros/ubuntu1604/zoneminder.links b/distros/beowulf/zoneminder.links similarity index 100% rename from distros/ubuntu1604/zoneminder.links rename to distros/beowulf/zoneminder.links diff --git a/distros/beowulf/zoneminder.linktrees b/distros/beowulf/zoneminder.linktrees new file mode 100644 index 000000000..8a9ca2723 --- /dev/null +++ b/distros/beowulf/zoneminder.linktrees @@ -0,0 +1,6 @@ +## 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/ubuntu1204/zoneminder.lintian-overrides b/distros/beowulf/zoneminder.lintian-overrides similarity index 100% rename from distros/ubuntu1204/zoneminder.lintian-overrides rename to distros/beowulf/zoneminder.lintian-overrides diff --git a/distros/ubuntu1604/zoneminder.logrotate b/distros/beowulf/zoneminder.logrotate similarity index 100% rename from distros/ubuntu1604/zoneminder.logrotate rename to distros/beowulf/zoneminder.logrotate diff --git a/distros/ubuntu1204/zoneminder.maintscript b/distros/beowulf/zoneminder.maintscript similarity index 100% rename from distros/ubuntu1204/zoneminder.maintscript rename to distros/beowulf/zoneminder.maintscript diff --git a/distros/ubuntu1204/zoneminder.manpages b/distros/beowulf/zoneminder.manpages similarity index 100% rename from distros/ubuntu1204/zoneminder.manpages rename to distros/beowulf/zoneminder.manpages diff --git a/distros/ubuntu1204/zoneminder.postinst b/distros/beowulf/zoneminder.postinst similarity index 89% rename from distros/ubuntu1204/zoneminder.postinst rename to distros/beowulf/zoneminder.postinst index 603786ff6..032595355 100644 --- a/distros/ubuntu1204/zoneminder.postinst +++ b/distros/beowulf/zoneminder.postinst @@ -39,9 +39,9 @@ if [ "$1" = "configure" ]; then exit 1; fi # This creates the user. - echo "grant lock tables, alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql + echo "grant lock tables, alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute, REFERENCES on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql else - echo "grant lock tables, alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost;" | mysql --defaults-file=/etc/mysql/debian.cnf mysql + echo "grant lock tables, alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute, REFERENCES on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost;" | mysql --defaults-file=/etc/mysql/debian.cnf mysql fi zmupdate.pl --nointeractive diff --git a/distros/ubuntu1204/zoneminder.postrm b/distros/beowulf/zoneminder.postrm similarity index 100% rename from distros/ubuntu1204/zoneminder.postrm rename to distros/beowulf/zoneminder.postrm diff --git a/distros/ubuntu1604/zoneminder.preinst b/distros/beowulf/zoneminder.preinst similarity index 100% rename from distros/ubuntu1604/zoneminder.preinst rename to distros/beowulf/zoneminder.preinst diff --git a/distros/ubuntu1604/zoneminder.tmpfile b/distros/beowulf/zoneminder.tmpfile similarity index 100% rename from distros/ubuntu1604/zoneminder.tmpfile rename to distros/beowulf/zoneminder.tmpfile diff --git a/distros/debian/changelog b/distros/debian/changelog index 46f1e2bd9..72841f158 100644 --- a/distros/debian/changelog +++ b/distros/debian/changelog @@ -40,7 +40,7 @@ zoneminder (1.27.99+1-testing-SNAPSHOT2014072901) testing; urgency=medium -- Isaac Connor Tue, 29 Jul 2014 14:50:20 -0400 -zoneminder (1.27.0+1-testing-v4ltomonitor-1) testing; urgency=high +zoneminder (1.27.0+1-trusty-v4ltomonitor-1) trusty; urgency=high * Snapshot release - 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/CMakeLists.txt b/distros/redhat/CMakeLists.txt index f1a1bc75b..e3e05c54e 100644 --- a/distros/redhat/CMakeLists.txt +++ b/distros/redhat/CMakeLists.txt @@ -7,12 +7,12 @@ # Display a message to show the RHEL build options are being processed. if(ZM_TARGET_DISTRO MATCHES "^el") - message([STATUS] "Starting RHEL Build Options" ...) + message([STATUS] "Starting RHEL Build Options" ...) elseif(ZM_TARGET_DISTRO MATCHES "^fc") - message([STATUS] "Starting Fedora Build Options" ...) -else(ZM_TARGET_DISTRO MATCHES "^el") - message([WARNING] "Unknown Build Option Detected" ...) -endif(ZM_TARGET_DISTRO MATCHES "^el") + message([STATUS] "Starting Fedora Build Options" ...) +else() + message([WARNING] "Unknown Build Option Detected" ...) +endif() # # CONFIGURE STAGE @@ -21,7 +21,7 @@ endif(ZM_TARGET_DISTRO MATCHES "^el") # Configure the common zoneminder files configure_file(common/zoneminder.logrotate.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.logrotate @ONLY) configure_file(common/zoneminder.service.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.service @ONLY) -file(MAKE_DIRECTORY sock swap zoneminder zoneminder-upload events temp) +file(MAKE_DIRECTORY sock swap zoneminder events temp) # Configure the Apache zoneminder files configure_file(httpd/zm-httpd.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zm-httpd.conf @ONLY) @@ -51,7 +51,6 @@ install(DIRECTORY sock swap DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSION install(DIRECTORY zoneminder DESTINATION /var/log DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY zoneminder DESTINATION /var/run DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY zoneminder DESTINATION /var/cache DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -install(DIRECTORY zoneminder-upload DESTINATION /var/spool DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY events temp DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) # Install the Apache zoneminder files diff --git a/distros/redhat/common/zoneminder.service.in b/distros/redhat/common/zoneminder.service.in index 8551a60e2..f4b3aad9b 100644 --- a/distros/redhat/common/zoneminder.service.in +++ b/distros/redhat/common/zoneminder.service.in @@ -5,6 +5,7 @@ Description=ZoneMinder CCTV recording and security system After=network.target mariadb.service Requires=mariadb.service +BindsTo=mariadb.service [Service] Type=forking diff --git a/distros/redhat/readme/README.httpd b/distros/redhat/readme/README.httpd index 1ba0ee688..32004a92d 100644 --- a/distros/redhat/readme/README.httpd +++ b/distros/redhat/readme/README.httpd @@ -15,9 +15,11 @@ NOTE: EL7 users should replace "dnf" with "yum" in the instructions below. set during the previous step, you will need to create the ZoneMinder database and configure a database account for ZoneMinder to use: - mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql - mysql -uroot -p -e "grant all on zm.* to \ - 'zmuser'@localhost identified by 'zmpass';" + mysql -u root -p < /usr/share/zoneminder/db/zm_create.sql + mysql -u root -p -e "CREATE USER 'zmuser'@'localhost' \ + IDENTIFIED BY 'zmpass';" + mysql -u root -p -e "GRANT ALL PRIVILEGES ON zm.* TO \ + 'zmuser'@localhost;" mysqladmin -uroot -p reload The database account credentials, zmuser/zmpass, are arbitrary. Set them to diff --git a/distros/redhat/readme/README.nginx b/distros/redhat/readme/README.nginx index 8bc3bbdc1..2a15d9544 100644 --- a/distros/redhat/readme/README.nginx +++ b/distros/redhat/readme/README.nginx @@ -13,9 +13,11 @@ New installs set during the previous step, you will need to create the ZoneMinder database and configure a database account for ZoneMinder to use: - mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql - mysql -uroot -p -e "grant all on zm.* to \ - 'zmuser'@localhost identified by 'zmpass';" + mysql -u root -p < /usr/share/zoneminder/db/zm_create.sql + mysql -u root -p -e "CREATE USER 'zmuser'@'localhost' \ + IDENTIFIED BY 'zmpass';" + mysql -u root -p -e "GRANT ALL PRIVILEGES ON zm.* TO \ + 'zmuser'@localhost;" mysqladmin -uroot -p reload The database account credentials, zmuser/zmpass, are arbitrary. Set them to @@ -44,10 +46,7 @@ New installs 5. Disable SELinux - We currently do not have the resources to create and maintain an accurate - SELinux policy for ZoneMinder on Fedora. We will gladly accept pull - reqeusts from anyone who wishes to do the work. In the meantime, SELinux - will need to be disabled or put into permissive mode. + SELinux must be disabled or put into permissive mode. This is not optional! To immediately disbale SELinux for the current seesion, issue the following from the command line: @@ -78,11 +77,20 @@ New installs sudo ln -sf /etc/zm/www/zoneminder.nginx.conf /etc/nginx/conf.d/ sudo ln -sf /etc/zm/www/redirect.nginx.conf /etc/nginx/default.d/ -7. Edit /etc/sysconfig/fcgiwrap and set DAEMON_PROCS to the maximum number of +7. Configure and start fcgiwrap + + Edit /etc/sysconfig/fcgiwrap and set DAEMON_PROCS to the maximum number of simulatneous streams the server should support. Generally, a good minimum value for this equals the total number of cameras you expect to view at the same time. + Enable the fcgiwrap *socket* in the following manner: + + sudo systemctl enable --now fcgiwrap@nginx.socket + + Do NOT try to start the fcgiwrap service! It must be triggered by the + socket to work properly. + 8. Now start the web server: sudo systemctl enable nginx diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 6715f7b26..f15e60326 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -3,51 +3,65 @@ %global zmgid_final apache # Crud is configured as a git submodule -%global crud_version 3.1.0-zm +%global crud_version 3.2.0 # CakePHP-Enum-Behavior is configured as a git submodule %global ceb_version 1.0-zm +# RtspServer is configured as a git submodule +%global rtspserver_commit cd7fd49becad6010a1b8466bfebbd93999a39878 + %global sslcert %{_sysconfdir}/pki/tls/certs/localhost.crt %global sslkey %{_sysconfdir}/pki/tls/private/localhost.key # 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.33.16 +Version: 1.37.11 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons -# Mootools is inder the MIT license: http://mootools.net/ +# jQuery is under the MIT license: https://jquery.org/license/ # CakePHP is under the MIT license: https://github.com/cakephp/cakephp # Crud is under the MIT license: https://github.com/FriendsOfCake/crud # CakePHP-Enum-Behavior is under the MIT license: https://github.com/asper/CakePHP-Enum-Behavior +# Bootstrap is under the MIT license: https://getbootstrap.com/docs/4.5/about/license/ +# Bootstrap-table is under the MIT license: https://bootstrap-table.com/docs/about/license/ +# font-awesome is under CC-BY license: https://fontawesome.com/license/free +# RtspServer is under the MIT license: https://github.com/PHZ76/RtspServer/blob/master/LICENSE License: GPLv2+ and LGPLv2+ and MIT URL: http://www.zoneminder.com/ Source0: https://github.com/ZoneMinder/ZoneMinder/archive/%{version}.tar.gz#/zoneminder-%{version}.tar.gz -Source1: https://github.com/ZoneMinder/crud/archive/v%{crud_version}.tar.gz#/crud-%{crud_version}.tar.gz +Source1: https://github.com/FriendsOfCake/crud/archive/v%{crud_version}.tar.gz#/crud-%{crud_version}.tar.gz Source2: https://github.com/ZoneMinder/CakePHP-Enum-Behavior/archive/%{ceb_version}.tar.gz#/cakephp-enum-behavior-%{ceb_version}.tar.gz +Source3: https://github.com/ZoneMinder/RtspServer/archive/%{rtspserver_commit}.tar.gz#/RtspServer-%{rtspserver_commit}.tar.gz +%{?rhel:BuildRequires: epel-rpm-macros} BuildRequires: systemd-devel BuildRequires: mariadb-devel BuildRequires: perl-podlators BuildRequires: polkit-devel -BuildRequires: cmake3 +BuildRequires: %{cmake_pkg_name} BuildRequires: gnutls-devel BuildRequires: bzip2-devel BuildRequires: pcre-devel @@ -84,10 +98,6 @@ BuildRequires: zlib-devel BuildRequires: ffmpeg BuildRequires: ffmpeg-devel -# Required for mp4 container support -BuildRequires: libmp4v2-devel -BuildRequires: x264-devel - # Allow existing user base to seamlessly transition to sub-packages Requires: %{name}-common%{?_isa} = %{version}-%{release} Requires: %{name}-httpd%{?_isa} = %{version}-%{release} @@ -111,8 +121,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 @@ -197,25 +207,30 @@ 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_UPLOAD_FTP_LOC_DIR %{_localstatedir}/spool/zoneminder-upload ./utils/zmeditconfigdata.sh ZM_OPT_CONTROL yes ./utils/zmeditconfigdata.sh ZM_CHECK_FOR_UPDATES no -./utils/zmeditconfigdata.sh ZM_DYN_SHOW_DONATE_REMINDER no -./utils/zmeditconfigdata.sh ZM_OPT_FAST_DELETE no %build -%cmake3 \ +# Disable LTO due to top level asm +# See https://fedoraproject.org/wiki/LTOByDefault +%define _lto_cflags %{nil} + +%cmake \ -DZM_WEB_USER="%{zmuid_final}" \ -DZM_WEB_GROUP="%{zmgid_final}" \ -DZM_TARGET_DISTRO="%{zmtargetdistro}" \ . -%make_build +%cmake_build %install -%make_install +%cmake_install desktop-file-install \ --dir %{buildroot}%{_datadir}/applications \ @@ -255,6 +270,32 @@ fi echo -e "\nVERY IMPORTANT: Before starting ZoneMinder, you must read the README file\nto finish the installation or upgrade!" echo -e "\nThe README file is located here: %{_pkgdocdir}-common/README\n" +# Neither the Apache nor Nginx packages create an SSL certificate anymore, so lets do that here +if [ -f %{sslkey} -o -f %{sslcert} ]; then + exit 0 +fi + +umask 077 +%{_bindir}/openssl genrsa -rand /proc/cpuinfo:/proc/filesystems:/proc/interrupts:/proc/ioports:/proc/uptime 2048 > %{sslkey} 2> /dev/null + +FQDN=`hostname` +# A >59 char FQDN means "root@FQDN" exceeds 64-char max length for emailAddress +if [ "x${FQDN}" = "x" -o ${#FQDN} -gt 59 ]; then + FQDN=localhost.localdomain +fi + +cat << EOF | %{_bindir}/openssl req -new -key %{sslkey} \ + -x509 -sha256 -days 365 -set_serial $RANDOM -extensions v3_req \ + -out %{sslcert} 2>/dev/null +-- +SomeState +SomeCity +SomeOrganization +SomeOrganizationalUnit +${FQDN} +root@${FQDN} +EOF + %post httpd # For the case of changing from nginx <-> httpd, files in these folders must change ownership if they exist %{_bindir}/chown -R %{zmuid_final}:%{zmgid_final} %{_sharedstatedir}/php/session/* >/dev/null 2>&1 || : @@ -286,32 +327,6 @@ ln -sf %{_sysconfdir}/zm/www/zoneminder.nginx.conf %{_sysconfdir}/zm/www/zonemin %{_bindir}/gpasswd -a nginx video >/dev/null 2>&1 || : %{_bindir}/gpasswd -a nginx dialout >/dev/null 2>&1 || : -# Nginx does not create an SSL certificate like the apache package does so lets do that here -if [ -f %{sslkey} -o -f %{sslcert} ]; then - exit 0 -fi - -umask 077 -%{_bindir}/openssl genrsa -rand /proc/apm:/proc/cpuinfo:/proc/dma:/proc/filesystems:/proc/interrupts:/proc/ioports:/proc/pci:/proc/rtc:/proc/uptime 2048 > %{sslkey} 2> /dev/null - -FQDN=`hostname` -# A >59 char FQDN means "root@FQDN" exceeds 64-char max length for emailAddress -if [ "x${FQDN}" = "x" -o ${#FQDN} -gt 59 ]; then - FQDN=localhost.localdomain -fi - -cat << EOF | %{_bindir}/openssl req -new -key %{sslkey} \ - -x509 -sha256 -days 365 -set_serial $RANDOM -extensions v3_req \ - -out %{sslcert} 2>/dev/null --- -SomeState -SomeCity -SomeOrganization -SomeOrganizationalUnit -${FQDN} -root@${FQDN} -EOF - %preun %systemd_preun %{name}.service @@ -340,7 +355,6 @@ EOF %{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy %{_bindir}/zmsystemctl.pl -%{_bindir}/zma %{_bindir}/zmaudit.pl %{_bindir}/zmc %{_bindir}/zmcontrol.pl @@ -357,8 +371,10 @@ EOF %{_bindir}/zmtelemetry.pl %{_bindir}/zmx10.pl %{_bindir}/zmonvif-probe.pl +%{_bindir}/zmonvif-trigger.pl %{_bindir}/zmstats.pl %{_bindir}/zmrecover.pl +%{_bindir}/zm_rtsp_server %{perl_vendorlib}/ZoneMinder* %{perl_vendorlib}/ONVIF* @@ -389,7 +405,6 @@ EOF %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/temp %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/cache/zoneminder %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/log/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/spool/zoneminder-upload %files nginx %config(noreplace) %attr(640,root,nginx) %{_sysconfdir}/zm/zm.conf @@ -413,32 +428,138 @@ EOF %dir %attr(755,nginx,nginx) %{_sharedstatedir}/zoneminder/temp %dir %attr(755,nginx,nginx) %{_localstatedir}/cache/zoneminder %dir %attr(755,nginx,nginx) %{_localstatedir}/log/zoneminder -%dir %attr(755,nginx,nginx) %{_localstatedir}/spool/zoneminder-upload %changelog -* Sun Dec 08 2019 Isaac Connor - 1.33.15-1 -- Bump to 1.33.15 Development +* Mon Jul 05 2021 Andrew Bauer - 1.37.1-1 +- 1.37.x development build -* Sun Aug 11 2019 Andrew Bauer - 1.33.14-1 -- Bump to 1.33.13 Development +* Tue Jun 22 2021 Andrew Bauer - 1.36.5-1 +- 1.36.5 release -* Sun Jul 07 2019 Andrew Bauer - 1.33.12-1 -- Bump to 1.33.12 Development +* 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 -* Sun Jun 23 2019 Andrew Bauer - 1.33.9-1 -- Bump to 1.33.9 Development +* Tue Jun 08 2021 Andrew Bauer - 1.36.4-1 +- 1.36.4 release -* Tue Apr 30 2019 Andrew Bauer - 1.33.8-1 -- Bump to 1.33.8 Development +* Sun May 30 2021 Andrew Bauer - 1.36.3-1 +- 1.36.3 release -* Sun Apr 07 2019 Andrew Bauer - 1.33.6-1 -- Bump to 1.33.6 Development +* Fri May 28 2021 Andrew Bauer - 1.36.2-1 +- 1.36.2 release -* Sat Mar 30 2019 Andrew Bauer - 1.33.4-1 -- Bump to 1.33.4 Development +* Fri May 21 2021 Andrew Bauer - 1.36.1-1 +- 1.36.1 release +- add rtspserver submodule -* Tue Dec 11 2018 Andrew Bauer - 1.33.0-1 -- Bump to 1.33.0 Development +* 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 + +* Fri Jan 31 2020 Andrew Bauer - 1.34.1-1 +- 1.34.1 Release + +* Sat Jan 18 2020 Andrew Bauer - 1.34.0-1 +- 1.34.0 Release + +* Tue Dec 17 2019 Leigh Scott - 1.32.3-5 +- Mass rebuild for x264 + +* Wed Aug 07 2019 Leigh Scott - 1.32.3-4 +- Rebuild for new ffmpeg version + +* Tue Mar 12 2019 Sérgio Basto - 1.32.3-3 +- Mass rebuild for x264 + +* Tue Mar 05 2019 RPM Fusion Release Engineering - 1.32.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild * Sat Dec 08 2018 Andrew Bauer - 1.32.3-1 - 1.32.3 Release diff --git a/distros/ubuntu1204/NEWS b/distros/ubuntu1204/NEWS deleted file mode 100644 index e69de29bb..000000000 diff --git a/distros/ubuntu1204/changelog b/distros/ubuntu1204/changelog deleted file mode 100644 index b19a3f228..000000000 --- a/distros/ubuntu1204/changelog +++ /dev/null @@ -1,8 +0,0 @@ -<<<<<<< HEAD -zoneminder (1.31.0-trusty) trusty; urgency=medium - - * placeholder - - -- Isaac Connor Fri, 13 May 2016 09:45:49 -0400 -======= ->>>>>>> master diff --git a/distros/ubuntu1204/source/lintian-overrides b/distros/ubuntu1204/source/lintian-overrides deleted file mode 100644 index 3669e5de8..000000000 --- a/distros/ubuntu1204/source/lintian-overrides +++ /dev/null @@ -1,9 +0,0 @@ -## Actually sources are there: "*-nc.js". -source-is-missing web/tools/mootools/mootools-*-yc.js - -## We're using "libjs-jquery" instead. -source-is-missing web/skins/*/js/jquery-1.4.2.min.js - -## Acknowledged, will repack eventually. -source-contains-prebuilt-javascript-object web/tools/mootools/mootools-*-yc.js -source-contains-prebuilt-javascript-object web/skins/*/js/jquery-1.4.2.min.js diff --git a/distros/ubuntu1204/watch b/distros/ubuntu1204/watch deleted file mode 100644 index 7ee690edb..000000000 --- a/distros/ubuntu1204/watch +++ /dev/null @@ -1,7 +0,0 @@ -version=3 - -opts=\ -repacksuffix=+dfsg,\ -dversionmangle=s{\+dfsg\d*}{},\ - https://github.com/ZoneMinder/ZoneMinder/releases \ - .*/ZoneMinder/archive/v(.*).tar.gz diff --git a/distros/ubuntu1204/zoneminder.links b/distros/ubuntu1204/zoneminder.links deleted file mode 100644 index 373548919..000000000 --- a/distros/ubuntu1204/zoneminder.links +++ /dev/null @@ -1 +0,0 @@ -/tmp/zm /usr/share/zoneminder/www/api/app/tmp diff --git a/distros/ubuntu1204/zoneminder.linktrees b/distros/ubuntu1204/zoneminder.linktrees deleted file mode 100644 index 2e843bbf1..000000000 --- a/distros/ubuntu1204/zoneminder.linktrees +++ /dev/null @@ -1,14 +0,0 @@ -## cakephp -#replace /usr/share/php/Cake /usr/share/zoneminder/www/api/lib/Cake - -## libjs-mootools -replace /usr/share/javascript/mootools/mootools.js /usr/share/zoneminder/www/tools/mootools/mootools-core.js -replace /usr/share/javascript/mootools/mootools.js /usr/share/zoneminder/www/tools/mootools/mootools-core-1.3.2-nc.js -replace /usr/share/javascript/mootools/mootools.js /usr/share/zoneminder/www/tools/mootools/mootools-core-1.3.2-yc.js -replace /usr/share/javascript/mootools/mootools-more.js /usr/share/zoneminder/www/tools/mootools/mootools-more.js -replace /usr/share/javascript/mootools/mootools-more.js /usr/share/zoneminder/www/tools/mootools/mootools-more-1.3.2.1-nc.js -replace /usr/share/javascript/mootools/mootools-more.js /usr/share/zoneminder/www/tools/mootools/mootools-more-1.3.2.1-yc.js - -## libjs-jquery -replace /usr/share/javascript/jquery/jquery.min.js /usr/share/zoneminder/www/skins/classic/js/jquery-1.4.2.min.js -replace /usr/share/javascript/jquery/jquery.min.js /usr/share/zoneminder/www/skins/flat/js/jquery-1.4.2.min.js diff --git a/distros/ubuntu1204/zoneminder.preinst b/distros/ubuntu1204/zoneminder.preinst deleted file mode 100644 index 3f75a1b3e..000000000 --- a/distros/ubuntu1204/zoneminder.preinst +++ /dev/null @@ -1,36 +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 - -abort=false -if [ -h /usr/share/zoneminder/www/events ]; then - l=$(readlink /usr/share/zoneminder/www/events) - if [ "$l" != "/var/cache/zoneminder/events" ]; then - abort=true - fi -fi -if [ -h /usr/share/zoneminder/www/images ]; then - l=$(readlink /usr/share/zoneminder/www/images ) - if [ "$l" != "/var/cache/zoneminder/images" ]; then - abort=true - fi -fi - -if [ "$abort" = "true" ]; then - cat >&2 << EOF -Aborting installation of zoneminder due to non-default symlinks in -/usr/share/zoneminder for the images and/or events directory, which could -result in loss of data. Please move your data in each of these directories to -/var/cache/zoneminder before installing zoneminder from the package. -EOF - exit 1 - -fi - -#DEBHELPER# diff --git a/distros/ubuntu1204/zoneminder.service b/distros/ubuntu1204/zoneminder.service deleted file mode 100644 index ac719b733..000000000 --- a/distros/ubuntu1204/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=/var/run/zm/zm.pid -Restart=always -RestartSec=10 -Environment=TZ=:/etc/localtime -TimeoutSec=600 - -[Install] -WantedBy=multi-user.target diff --git a/distros/ubuntu1204/zoneminder.tmpfile b/distros/ubuntu1204/zoneminder.tmpfile deleted file mode 100644 index 017955900..000000000 --- a/distros/ubuntu1204/zoneminder.tmpfile +++ /dev/null @@ -1,3 +0,0 @@ -d /var/run/zm 0755 www-data www-data -d /tmp/zm 0755 www-data www-data -d /var/tmp/zm 0755 www-data www-data diff --git a/distros/ubuntu1410/README.Debian b/distros/ubuntu1410/README.Debian deleted file mode 100644 index a49b6be72..000000000 --- a/distros/ubuntu1410/README.Debian +++ /dev/null @@ -1,51 +0,0 @@ -zoneminder for Debian ---------------------- - -There is one manual step to get the web interface working. -You need to link /etc/zm/apache.conf to /etc/apache2/conf.d/zoneminder.conf, -then reload the apache config (i.e. /etc/init.d/apache2 reload) - -Changing the location for images and events -------------------------------------------- - -Zoneminder, in its upstream form, stores data in /usr/share/zoneminder/. This -package modifies that by changing /usr/share/zoneminder/images and -/usr/share/zoneminder/events to symlinks to directories under -/var/cache/zoneminder. - -There are numerous places these could be put and ways to do it. But, at the -moment, if you change this, an upgrade will fail with a warning about these -locations having changed (the reason for this was that previously, an upgrade -would silently revert the changes and cause event loss - refer -bug #608793). - -If you do want to change the location, here are a couple of suggestions. - -These lines would mount /dev/sdX1 to /video_storage, and then 'link' /video_storage -to the locations that ZoneMinder expects them to be at. - - /dev/sdX1 /video_storage ext4 defaults 0 2 - /video_storage/zoneminder/images /var/cache/zoneminder/images none bind 0 2 - /video_storage/zoneminder/events /var/cache/zoneminder/events none bind 0 2 - - or if you have a separate partition for each: - - /dev/sdX1 /var/cache/zoneminder/images ext4 defaults 0 2 - /dev/sdX2 /var/cache/zoneminder/events ext4 defaults 0 2 - - - - -- Peter Howard , Sun, 16 Jan 2010 01:35:51 +1100 - -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/ubuntu1410/apache.conf b/distros/ubuntu1410/apache.conf deleted file mode 100644 index 92a2b6414..000000000 --- a/distros/ubuntu1410/apache.conf +++ /dev/null @@ -1,9 +0,0 @@ -Alias /zm /usr/share/zoneminder - - - php_flag register_globals off - Options Indexes FollowSymLinks - - DirectoryIndex index.php - - diff --git a/distros/ubuntu1410/changelog b/distros/ubuntu1410/changelog deleted file mode 100644 index 55e3d17b0..000000000 --- a/distros/ubuntu1410/changelog +++ /dev/null @@ -1,323 +0,0 @@ -zoneminder (1.30.2-trusty-2016033001) trusty; urgency=medium - - * merge master - - -- Isaac Connor Wed, 30 Mar 2016 14:09:48 -0400 - -zoneminder (1.30.2-trusty-2016032901) trusty; urgency=medium - - * filter fixes, merge options rework by Kyle - - -- Isaac Connor Tue, 29 Mar 2016 12:27:57 -0400 - -zoneminder (1.30.2-trusty-2016030702) trusty; urgency=medium - - * - - -- Isaac Connor Mon, 07 Mar 2016 22:14:03 -0500 - -zoneminder (1.30.2-trusty-2016030701) trusty; urgency=medium - - * merge master. with telemetry - - -- Isaac Connor Mon, 07 Mar 2016 21:47:53 -0500 - -zoneminder (1.30.2-trusty-2016022101) trusty; urgency=medium - - * merge zmtrigger fix - - -- Isaac Connor Mon, 22 Feb 2016 09:15:53 -0500 - -zoneminder (1.30.2-trusty-2016021901) trusty; urgency=medium - - * zmtrigger improvements - - -- Isaac Connor Fri, 19 Feb 2016 11:09:57 -0500 - -zoneminder (1.30.2-trusty-2016021701) trusty; urgency=medium - - * printout id, and ip address when failing to connect - - -- Isaac Connor Wed, 17 Feb 2016 09:40:49 -0500 - -zoneminder (1.30.2-trusty-2016021001) trusty; urgency=medium - - * - - -- Isaac Connor Wed, 10 Feb 2016 13:06:09 -0500 - -zoneminder (1.29.111-trusty-2016020101) trusty; urgency=medium - - * Fix video download and use of Storage Areas - - -- Isaac Connor Mon, 01 Feb 2016 13:42:06 -0500 - -zoneminder (1.29.111-trusty-2016011401) trusty; urgency=medium - - * fix timeline view for storageareas - - -- Isaac Connor Thu, 14 Jan 2016 14:03:41 -0500 - -zoneminder (1.29.111-trusty-2016010801) trusty; urgency=medium - - * Add better debug and skip when event links are not just digits. Merge multi-server stuff from master. - - -- Isaac Connor Fri, 08 Jan 2016 10:37:16 -0500 - -zoneminder (1.29.111-trusty-2016010401) trusty; urgency=medium - - * include fix to rotate image dimensions when applying a rotation - - -- Isaac Connor Mon, 04 Jan 2016 13:24:42 -0500 - -zoneminder (1.29.111-trusty-2016010101) trusty; urgency=medium - - * fix logging with multi-server - - -- Isaac Connor Fri, 01 Jan 2016 17:11:09 -0500 - -zoneminder (1.29.111-trusty-2015123101) trusty; urgency=medium - - * Add log filtering from multi-server - - -- Isaac Connor Thu, 31 Dec 2015 10:18:03 -0500 - -zoneminder (1.29.109-trusty-2015122401) trusty; urgency=medium - - * fix delete events not in database in zmaudit.pl - - -- Isaac Connor Thu, 24 Dec 2015 12:38:05 -0500 - -zoneminder (1.29.109-trusty-2015122301) trusty; urgency=medium - - * todays release - - -- Isaac Connor Wed, 23 Dec 2015 09:33:46 -0500 - -zoneminder (1.29.109-trusty-2015122202) trusty; urgency=medium - - * more object work and zmaudit - - -- Isaac Connor Tue, 22 Dec 2015 13:13:56 -0500 - -zoneminder (1.29.109-trusty-2015122201) trusty; urgency=medium - - * merge multi-server, and master stuff, start work on zmaudit storage areas support - - -- Isaac Connor Tue, 22 Dec 2015 11:11:44 -0500 - -zoneminder (1.29.109-trusty-2015122103) trusty; urgency=medium - - * Fixes - - -- Isaac Connor Mon, 21 Dec 2015 15:20:15 -0500 - -zoneminder (1.29.109-trusty-2015122102) trusty; urgency=medium - - * fix deleting events - - -- Isaac Connor Mon, 21 Dec 2015 14:49:12 -0500 - -zoneminder (1.29.109-trusty-2015122101) trusty; urgency=medium - - * Make zmfilter work. - - -- Isaac Connor Mon, 21 Dec 2015 12:32:27 -0500 - -zoneminder (1.29.109-trusty-2015121803) trusty; urgency=medium - - * merge zmvideo improvements - - -- Isaac Connor Fri, 18 Dec 2015 14:17:56 -0500 - -zoneminder (1.29.109-trusty-2015121802) trusty; urgency=medium - - * Add some missing files to the autoconf build - - -- Isaac Connor Fri, 18 Dec 2015 12:14:08 -0500 - -zoneminder (1.29.109-trusty-2015121801) trusty; urgency=medium - - * - - -- Isaac Connor Fri, 18 Dec 2015 11:05:58 -0500 - -zoneminder (1.29.109-trusty-2015121701) trusty; urgency=medium - - * Merge master + better zmvideo - - -- Isaac Connor Thu, 17 Dec 2015 15:11:18 -0500 - -zoneminder (1.29.0-trusty-2015112301) trusty; urgency=medium - - * apply fix for zms crash - - -- Isaac Connor Mon, 23 Nov 2015 10:47:49 -0500 - -zoneminder (1.29.0-trusty-2015110601) trusty; urgency=medium - - * add a FIONREAD test on timeout - - -- Isaac Connor Wed, 22 Jul 2015 09:55:37 -0400 - -zoneminder (1.29.0-trusty-2015072201) trusty; urgency=medium - - * add AnalysisFPS - - -- Isaac Connor Wed, 22 Jul 2015 09:55:37 -0400 - -zoneminder (1.29.0-trusty-2015071601) trusty; urgency=medium - - * Merge master and zmtrigger - - -- Isaac Connor Thu, 16 Jul 2015 13:15:58 -0400 - -zoneminder (1.29.0-trusty-38) trusty; urgency=medium - - * Merge master - - -- Isaac Connor Tue, 14 Jul 2015 10:15:00 -0400 - -zoneminder (1.29.0-trusty-37) trusty; urgency=medium - - * merge master api stuff, set sleep after failure to capture to 5000 instead of 5000000 - - -- Isaac Connor Fri, 19 Jun 2015 09:59:54 -0400 - -zoneminder (1.29.0-trusty-36) trusty; urgency=medium - - * Detect select interuption when no action on our fd - - -- Isaac Connor Thu, 28 May 2015 09:35:59 -0400 - -zoneminder (1.29.0-trusty-35) trusty; urgency=medium - - * better logging - - -- Isaac Connor Fri, 22 May 2015 15:30:42 -0400 - -zoneminder (1.29.0-trusty-34) trusty; urgency=medium - - * Faster shutdown and changes to ReadData - - -- Isaac Connor Thu, 21 May 2015 15:54:47 -0400 - -zoneminder (1.29.0-trusty-33) trusty; urgency=medium - - * update zmaudit some more - - -- Isaac Connor Thu, 14 May 2015 13:44:35 -0400 - -zoneminder (1.29.0-trusty-32) trusty; urgency=medium - - * merge zmaudit_updates1 - - -- Isaac Connor Wed, 13 May 2015 15:51:56 -0400 - -zoneminder (1.29.0-trusty-31) trusty; urgency=medium - - * Merge some fixes and zmaudit improvements - - -- Isaac Connor Wed, 13 May 2015 15:16:19 -0400 - -zoneminder (1.29.0-trusty-27) trusty; urgency=medium - - * fflush logs, merge master. - - -- Isaac Connor Mon, 30 Mar 2015 19:28:05 -0400 - -zoneminder (1.29.0-vivid-26) vivid; urgency=medium - - * logging improvements, merge RedData changes - - -- Isaac Connor Wed, 04 Mar 2015 16:39:19 -0500 - -zoneminder (1.29.0-vivid-25) vivid; urgency=medium - - * some change to ReadData - - -- Isaac Connor Mon, 02 Mar 2015 12:57:16 -0500 - -zoneminder (1.29.0-utopic-24) utopic; urgency=medium - - * merge local_raw - - -- Isaac Connor Mon, 23 Feb 2015 17:49:36 -0500 - -zoneminder (1.29.0-trusty-23) trusty; urgency=medium - - * more onvif merges, fix to zmfilter - - -- Isaac Connor Sat, 21 Feb 2015 16:17:01 -0500 - -zoneminder (1.29.0-trusty-22) trusty; urgency=medium - - * updates from master: merge onvif, default to classic if skin not defined. - - -- Isaac Connor Thu, 19 Feb 2015 18:14:58 -0500 - -zoneminder (1.29.0-utopic-21) utopic; urgency=medium - - * updates from master, improve monitor probing and support TRENDnet cameras - - -- Isaac Connor Tue, 17 Feb 2015 14:18:52 -0500 - -zoneminder (1.29.0-wheezy-20) wheezy; urgency=medium - - * zmaudit.pl improvements - double check db before deleting fs event - - -- Isaac Connor Wed, 04 Feb 2015 11:09:22 -0500 - -zoneminder (1.29.0-utopic-18) utopic; urgency=medium - - * RTSP Timeout fixes - - -- Isaac Connor Wed, 28 Jan 2015 13:49:16 -0500 - -zoneminder (1.29.0-utopic-17) utopic; urgency=medium - - * Merge master, use new split-up debian build - - -- Isaac Connor Mon, 26 Jan 2015 11:21:07 -0500 - -zoneminder (1.28.0+nmu1) testing; urgency=medium - - * Non-maintainer upload - * Split the debian package into several packages - * Switch to native source format - - -- Emmanuel Papin Thu, 15 Jan 2015 20:00:08 +0100 - -zoneminder (1.28.0-0.2) testing; urgency=medium - - * Non-maintainer upload. - * Upstream release for debian jessie - * Package dependencies updated - * debhelper version upgraded - * Standards-Version upgraded - * Use debhelper commands instead of standard commands - * Install man pages in /usr/share/man (patch added) - * Switch to quilt - * Switch to systemd - * Some lintian fixes - - -- Emmanuel Papin Wed, 26 Nov 2014 00:26:01 +0100 - -zoneminder (1.28.0-0.1) stable; urgency=medium - - * Release - - -- Isaac Connor Fri, 17 Oct 2014 09:27:22 -0400 - -zoneminder (1.27.99+1-testing-SNAPSHOT2014072901) testing; urgency=medium - - * improve error messages - * Make zmupdate re-run the most recent patch so that people running the daily builds get their db updates - - -- Isaac Connor Tue, 29 Jul 2014 14:50:20 -0400 - -zoneminder (1.27.0+1-testing-v4ltomonitor-1) testing; urgency=high - - * Snapshot release - - - -- Isaac Connor Wed, 09 Jul 2014 21:35:29 -0400 diff --git a/distros/ubuntu1410/compat b/distros/ubuntu1410/compat deleted file mode 100644 index ec635144f..000000000 --- a/distros/ubuntu1410/compat +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/distros/ubuntu1410/control b/distros/ubuntu1410/control deleted file mode 100644 index 5a873f133..000000000 --- a/distros/ubuntu1410/control +++ /dev/null @@ -1,122 +0,0 @@ -Source: zoneminder -Section: net -Priority: optional -Maintainer: Isaac Connor -Build-Depends: debhelper (>= 9), po-debconf (>= 1.0), autoconf, automake, libphp-serialization-perl, libgnutls-dev, libmysqlclient-dev | libmariadbclient-dev, libdbd-mysql-perl, libdate-manip-perl, libwww-perl, libjpeg8-dev | libjpeg9-dev | libjpeg62-turbo-dev, libpcre3-dev, libavcodec-dev, libavformat-dev (>= 3:0.svn20090204), libswscale-dev (>= 3:0.svn20090204), libavutil-dev, libv4l-dev (>= 0.8.3), libbz2-dev, libtool, libsys-mmap-perl, libavdevice-dev, libdevice-serialport-perl, libarchive-zip-perl, libmime-lite-perl, dh-autoreconf, libvlccore-dev, libvlc-dev, libcurl4-gnutls-dev | libcurl4-nss-dev | libcurl4-openssl-dev, libgcrypt11-dev | libgcrypt20-dev, libpolkit-gobject-1-dev, libdbi-perl, libnet-sftp-foreign-perl, libexpect-perl, libmime-tools-perl, libx264-dev, libmp4v2-dev, libpcre3-dev -Standards-Version: 3.9.6 - -Package: zoneminder -Section: metapackages -Architecture: all -Depends: ${misc:Depends}, - libzoneminder-perl (>= ${source:Version}), - zoneminder-database (>= ${source:Version}), - zoneminder-core (>= ${binary:Version}), - zoneminder-ui-base (>= ${source:Version}), - zoneminder-ui-classic (>= ${source:Version}), - zoneminder-ui-mobile (>= ${source:Version}), - zoneminder-ui-xml (>= ${source:Version}) -Description: Video camera security and surveillance solution (metapackage) - 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 -Depends: ${misc:Depends}, ${perl:Depends}, libdbi-perl, - libdevice-serialport-perl, libimage-info-perl, libjson-any-perl, libjson-maybexs-perl, - libsys-mmap-perl, liburi-encode-perl, libwww-perl -Description: Perl libraries for ZoneMinder - ZoneMinder is a video camera security and surveillance solution. - . - This package provides the libraries for the perl scripts, it can be used to - write custom interfaces as well. - -Package: zoneminder-database -Section: database -Architecture: all -Depends: ${misc:Depends}, debconf, dbconfig-common, - mysql-client | mariadb-client -Recommends: mysql-server | mariadb-server -Description: Database management package for ZoneMinder - ZoneMinder is a video camera security and surveillance solution. - . - This package provides the sql files and maintenance scripts to perform all the - database operations (installation, upgrade or removal) on a local or a remote - server. - -Package: zoneminder-core -Section: video -Architecture: any -Depends: libzoneminder-perl (= ${source:Version}), - zoneminder-database (= ${source:Version}), ${shlibs:Depends}, ${misc:Depends}, - ${perl:Depends}, libarchive-tar-perl, libarchive-zip-perl, libdate-manip-perl, - libdbi-perl, libmodule-load-conditional-perl, libmime-lite-perl, - libmime-tools-perl, libnet-sftp-foreign-perl, libphp-serialization-perl, - debconf, ffmpeg | libav-tools, rsyslog | system-log-daemon, zip, - policykit-1, apache2, libmp4v2-2, libpcre++0 -Description: Core binaries and perl scripts for ZoneMinder - ZoneMinder is a video camera security and surveillance solution. - . - This package provides the executable compiled binaries which do the main video - processing work and the perl scripts which perform helper and/or external - interface tasks. - -Package: zoneminder-core-dbg -Priority: extra -Section: debug -Architecture: any -Depends: zoneminder-core (= ${binary:Version}), ${misc:Depends} -Description: Debugging symbols for ZoneMinder - ZoneMinder is a video camera security and surveillance solution. - . - This package provides the debugging symbols for the executable compiled - binaries. - -Package: zoneminder-ui-base -Section: web -Architecture: any -Depends: zoneminder-core (= ${binary:Version}), ${shlibs:Depends}, - ${misc:Depends}, debconf, apache2, libapache2-mod-php5 | libapache2-mod-fcgid, - php5, php5-mysql | php5-mysqlnd, php5-gd -Description: Essential files for ZoneMinder's web user interface - ZoneMinder is a video camera security and surveillance solution. - . - This package provides the essential web files and maintenance scripts to set up - a basic web environment. - -Package: zoneminder-ui-classic -Section: web -Architecture: all -Depends: zoneminder-ui-base (>= ${source:Version}), ${misc:Depends} -Description: Classic web user interface for ZoneMinder - ZoneMinder is a video camera security and surveillance solution. - . - This package provides the classic web user interface. - -Package: zoneminder-ui-mobile -Section: web -Architecture: all -Depends: zoneminder-ui-base (>= ${source:Version}), ${misc:Depends} -Description: Mobile web user interface for ZoneMinder - ZoneMinder is a video camera security and surveillance solution. - . - This package provides the web user interface for mobile devices. - -Package: zoneminder-ui-xml -Section: web -Architecture: all -Depends: zoneminder-ui-base (>= ${source:Version}), ${misc:Depends} -Description: XML interface for ZoneMinder - ZoneMinder is a video camera security and surveillance solution. - . - This package provides a XML interface mainly intended for use with the eyeZm - iPhone Application, but can be used with any other custom programs as well. diff --git a/distros/ubuntu1410/copyright b/distros/ubuntu1410/copyright deleted file mode 100644 index a177502a0..000000000 --- a/distros/ubuntu1410/copyright +++ /dev/null @@ -1,22 +0,0 @@ -Copyright: - -Copyright 2002 Philip Coombes - -License: - -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 - -On Debian GNU/Linux systems, the text of the GPL can be found in -/usr/share/common-licenses/GPL. diff --git a/distros/ubuntu1410/docs b/distros/ubuntu1410/docs deleted file mode 100644 index b43bf86b5..000000000 --- a/distros/ubuntu1410/docs +++ /dev/null @@ -1 +0,0 @@ -README.md diff --git a/distros/ubuntu1410/libzoneminder-perl.install b/distros/ubuntu1410/libzoneminder-perl.install deleted file mode 100644 index 792ffc15e..000000000 --- a/distros/ubuntu1410/libzoneminder-perl.install +++ /dev/null @@ -1,4 +0,0 @@ -usr/share/perl5/ZoneMinder -usr/share/perl5/ZoneMinder.pm -debian/tmp/usr/share/man/man3/ZoneMinder.3pm -debian/tmp/usr/share/man/man3/ZoneMinder::* diff --git a/distros/ubuntu1410/po/POTFILES.in b/distros/ubuntu1410/po/POTFILES.in deleted file mode 100644 index 5b155907e..000000000 --- a/distros/ubuntu1410/po/POTFILES.in +++ /dev/null @@ -1,3 +0,0 @@ -[type: gettext/rfc822deb] zoneminder-core.templates -[type: gettext/rfc822deb] zoneminder-database.templates -[type: gettext/rfc822deb] zoneminder-ui-base.templates diff --git a/distros/ubuntu1410/po/fr.po b/distros/ubuntu1410/po/fr.po deleted file mode 100644 index 85ced7fd2..000000000 --- a/distros/ubuntu1410/po/fr.po +++ /dev/null @@ -1,252 +0,0 @@ -# debconf french translation file for ZoneMinder. -# Copyright (C) 2001-2008 Philip Coombes -# This file is distributed under the same license as the zoneminder package. -# First author: Emmanuel Papin , 2014. -# -msgid "" -msgstr "" -"Project-Id-Version: zoneminder\n" -"Report-Msgid-Bugs-To: zoneminder@packages.debian.org\n" -"POT-Creation-Date: 2014-12-16 12:34+0100\n" -"PO-Revision-Date: 2014-12-07 00:40+0100\n" -"Last-Translator: Emmanuel Papin \n" -"Language-Team: French \n" -"Language: fr\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#. Type: boolean -#. Description -#: ../zoneminder-core.templates:1001 -msgid "Delete this non empty directory?" -msgstr "Supprimer ce répertoire non vide ?" - -#. Type: boolean -#. Description -#: ../zoneminder-core.templates:1001 -msgid "" -"A purge of the ZoneMinder package is performed but the directory '/var/cache/" -"zoneminder' is not empty so it will not be deleted." -msgstr "" -"Une purge du paquet ZoneMinder est en cours mais le répertoire '/var/cache/" -"zoneminder' n'est pas vide et sera donc conservé." - -#. Type: boolean -#. Description -#: ../zoneminder-core.templates:1001 -msgid "" -"Please consider that this directory is designed to contain data resulting " -"from event detection. Therefore, \"proof of evidence\" could be lost!\"" -msgstr "" -"Veuillez considérer que ce répertoire est conçu pour contenir des données " -"résultants de la détection d'événements. Par conséquent, des preuves " -"pourraient être perdues !" - -#. Type: boolean -#. Description -#: ../zoneminder-core.templates:1001 -msgid "" -"If you are not sure of your decision, please do not delete this directory " -"but perform a manual checkup." -msgstr "" -"Si vous n'êtes pas sûr de votre décision, veuillez conserver ce répertoire " -"et effectuer une vérification manuelle." - -#. Type: boolean -#. Description -#: ../zoneminder-core.templates:2001 -msgid "Deletion confirmed?" -msgstr "Supression confirmée ?" - -#. Type: boolean -#. Description -#: ../zoneminder-core.templates:2001 -msgid "" -"You have allowed the deletion of directory '/var/cache/zoneminder' although " -"it may contain critical data." -msgstr "" -"Vous avez autorisé la suppression du répertoire '/var/cache/zoneminder' bien " -"qu'il puisse contenir des données critiques." - -#. Type: select -#. Choices -#: ../zoneminder-database.templates:1001 -msgid "local" -msgstr "local" - -#. Type: select -#. Choices -#: ../zoneminder-database.templates:1001 -msgid "remote" -msgstr "distant" - -#. Type: select -#. Description -#: ../zoneminder-database.templates:1002 -msgid "Database location:" -msgstr "Emplacement de la base de donnée :" - -#. Type: select -#. Description -#: ../zoneminder-database.templates:1002 -msgid "" -"A database server is required to run ZoneMinder. The database can be " -"installed either locally or remotely on a machine of your network." -msgstr "" -"Un serveur de base de données est requis pour ZoneMinder. La base de donnée " -"peut être installée localement ou à distance sur une machine de votre réseau." - -#. Type: select -#. Description -#: ../zoneminder-database.templates:1002 -msgid "" -"If you choose a remote location, you will have to select the 'tcp/ip' " -"connection method and enter the hostname or ip address of the remote machine " -"in the next configuration screens." -msgstr "" -"Si vous choisissez un emplacement distant, vous devrez sélectionner la " -"méthode de connexion 'tcp/ip' et entrer le nom réseau ou l'adresse ip de la " -"machine distante dans les écrans de configuration suivants." - -#. Type: error -#. Description -#: ../zoneminder-database.templates:2001 -msgid "No local database server is available:" -msgstr "Aucun serveur local de base de données n'est disponible :" - -#. Type: error -#. Description -#: ../zoneminder-database.templates:2001 -msgid "" -"Currently ZoneMinder supports mysql or mariadb database server but none of " -"them appears to be installed on this machine." -msgstr "" -"Actuellement ZoneMinder supporte les serveurs de base de données mysql et " -"mariadb mais aucun d'entre eux n'est installé sur cette machine." - -#. Type: error -#. Description -#: ../zoneminder-database.templates:2001 -msgid "" -"In order to complete ZoneMinder's installation, after ending of this " -"assistant, please install a compatible database server and then restart the " -"assistant by invoking:" -msgstr "" -"Afin de compléter l'installation de ZoneMinder, après la fermeture de cet " -"assitant, veuillez installer un serveur de base de données compatible et " -"ensuite redémarrez l'assistant en invoquant :" - -#. Type: error -#. Description -#. Type: error -#. Description -#: ../zoneminder-database.templates:2001 ../zoneminder-database.templates:3001 -msgid "$ sudo dpkg-reconfigure zoneminder" -msgstr "$ sudo dpkg-reconfigure zoneminder" - -#. Type: error -#. Description -#: ../zoneminder-database.templates:3001 -msgid "Remote database servers are not allowed:" -msgstr "Les serveurs de base de données distants ne sont pas autorisés :" - -#. Type: error -#. Description -#: ../zoneminder-database.templates:3001 -msgid "" -"The current configuration of dbconfig-common does not allow installation of " -"a database on remote servers." -msgstr "" -"La configuration actuelle de dbconfig-common ne permet pas l'installation de " -"bases de données sur des serveurs distants." - -#. Type: error -#. Description -#: ../zoneminder-database.templates:3001 -msgid "" -"In order to reconfigure dbconfig-common, please invoke the following command " -"after ending of this assistant:" -msgstr "" -"Afin de reconfigurer dbconfig-common, veuillez invoquer la commande suivante " -"après la fermeture de cet assitant :" - -#. Type: error -#. Description -#: ../zoneminder-database.templates:3001 -msgid "$ sudo dpkg-reconfigure dbconfig-common" -msgstr "$ sudo dpkg-reconfigure dbconfig-common" - -#. Type: error -#. Description -#: ../zoneminder-database.templates:3001 -msgid "" -"Then, to complete ZoneMinder's installation, please restart this assistant " -"by invoking:" -msgstr "" -"Ensuite, pour compléter l'installation de ZoneMinder, veuillez redémarrer " -"cet assistant en invoquant :" - -#. Type: password -#. Description -#: ../zoneminder-database.templates:4001 -msgid "New password for the ZoneMinder 'admin' user:" -msgstr "Nouveau mot de passe pour le compte 'admin' de ZoneMinder :" - -#. Type: password -#. Description -#: ../zoneminder-database.templates:4001 -msgid "Please enter the password of the default administrative user." -msgstr "Veuillez entrer le mot de passe du compte administrateur par défaut." - -#. Type: password -#. Description -#: ../zoneminder-database.templates:4001 -msgid "" -"While not mandatory, it is highly recommended that you set a custom password " -"for the administrative 'admin' user." -msgstr "" -"Bien que cela ne soit pas obligatoire, il est fortement recommandé de " -"fournir un mot de passe personnalisé pour le compte administrateur 'admin'." - -#. Type: password -#. Description -#: ../zoneminder-database.templates:4001 -msgid "If this field is left blank, the password will not be changed." -msgstr "Si le champ est laissé vide, le mot de passe ne sera pas changé." - -#. Type: password -#. Description -#: ../zoneminder-database.templates:5001 -msgid "Repeat password for the ZoneMinder 'admin' user:" -msgstr "Répéter le mot de passe pour le compte 'admin' de ZoneMinder :" - -#. Type: error -#. Description -#: ../zoneminder-database.templates:6001 -msgid "Password input error" -msgstr "Erreur de mot de passe" - -#. Type: error -#. Description -#: ../zoneminder-database.templates:6001 -msgid "The two passwords you entered were not the same. Please try again." -msgstr "" -"Les deux mots de passe saisis ne sont pas les mêmes. Veuillez essayer à " -"nouveau." - -#. Type: multiselect -#. Description -#: ../zoneminder-ui-base.templates:1001 -msgid "Web server to reconfigure automatically:" -msgstr "Serveur web à reconfigurer automatiquement :" - -#. Type: multiselect -#. Description -#: ../zoneminder-ui-base.templates:1001 -msgid "" -"Please choose the web server that should be automatically configured for " -"ZoneMinder's web portal access." -msgstr "" -"Veuillez choisir le serveur web à reconfigurer automatiquement pour l'accès " -"au portail web de ZoneMinder." diff --git a/distros/ubuntu1410/po/templates.pot b/distros/ubuntu1410/po/templates.pot deleted file mode 100644 index 941a4094e..000000000 --- a/distros/ubuntu1410/po/templates.pot +++ /dev/null @@ -1,222 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: zoneminder\n" -"Report-Msgid-Bugs-To: zoneminder@packages.debian.org\n" -"POT-Creation-Date: 2014-12-16 12:34+0100\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=CHARSET\n" -"Content-Transfer-Encoding: 8bit\n" - -#. Type: boolean -#. Description -#: ../zoneminder-core.templates:1001 -msgid "Delete this non empty directory?" -msgstr "" - -#. Type: boolean -#. Description -#: ../zoneminder-core.templates:1001 -msgid "" -"A purge of the ZoneMinder package is performed but the directory '/var/cache/" -"zoneminder' is not empty so it will not be deleted." -msgstr "" - -#. Type: boolean -#. Description -#: ../zoneminder-core.templates:1001 -msgid "" -"Please consider that this directory is designed to contain data resulting " -"from event detection. Therefore, \"proof of evidence\" could be lost!\"" -msgstr "" - -#. Type: boolean -#. Description -#: ../zoneminder-core.templates:1001 -msgid "" -"If you are not sure of your decision, please do not delete this directory " -"but perform a manual checkup." -msgstr "" - -#. Type: boolean -#. Description -#: ../zoneminder-core.templates:2001 -msgid "Deletion confirmed?" -msgstr "" - -#. Type: boolean -#. Description -#: ../zoneminder-core.templates:2001 -msgid "" -"You have allowed the deletion of directory '/var/cache/zoneminder' although " -"it may contain critical data." -msgstr "" - -#. Type: select -#. Choices -#: ../zoneminder-database.templates:1001 -msgid "local" -msgstr "" - -#. Type: select -#. Choices -#: ../zoneminder-database.templates:1001 -msgid "remote" -msgstr "" - -#. Type: select -#. Description -#: ../zoneminder-database.templates:1002 -msgid "Database location:" -msgstr "" - -#. Type: select -#. Description -#: ../zoneminder-database.templates:1002 -msgid "" -"A database server is required to run ZoneMinder. The database can be " -"installed either locally or remotely on a machine of your network." -msgstr "" - -#. Type: select -#. Description -#: ../zoneminder-database.templates:1002 -msgid "" -"If you choose a remote location, you will have to select the 'tcp/ip' " -"connection method and enter the hostname or ip address of the remote machine " -"in the next configuration screens." -msgstr "" - -#. Type: error -#. Description -#: ../zoneminder-database.templates:2001 -msgid "No local database server is available:" -msgstr "" - -#. Type: error -#. Description -#: ../zoneminder-database.templates:2001 -msgid "" -"Currently ZoneMinder supports mysql or mariadb database server but none of " -"them appears to be installed on this machine." -msgstr "" - -#. Type: error -#. Description -#: ../zoneminder-database.templates:2001 -msgid "" -"In order to complete ZoneMinder's installation, after ending of this " -"assistant, please install a compatible database server and then restart the " -"assistant by invoking:" -msgstr "" - -#. Type: error -#. Description -#. Type: error -#. Description -#: ../zoneminder-database.templates:2001 ../zoneminder-database.templates:3001 -msgid "$ sudo dpkg-reconfigure zoneminder" -msgstr "" - -#. Type: error -#. Description -#: ../zoneminder-database.templates:3001 -msgid "Remote database servers are not allowed:" -msgstr "" - -#. Type: error -#. Description -#: ../zoneminder-database.templates:3001 -msgid "" -"The current configuration of dbconfig-common does not allow installation of " -"a database on remote servers." -msgstr "" - -#. Type: error -#. Description -#: ../zoneminder-database.templates:3001 -msgid "" -"In order to reconfigure dbconfig-common, please invoke the following command " -"after ending of this assistant:" -msgstr "" - -#. Type: error -#. Description -#: ../zoneminder-database.templates:3001 -msgid "$ sudo dpkg-reconfigure dbconfig-common" -msgstr "" - -#. Type: error -#. Description -#: ../zoneminder-database.templates:3001 -msgid "" -"Then, to complete ZoneMinder's installation, please restart this assistant " -"by invoking:" -msgstr "" - -#. Type: password -#. Description -#: ../zoneminder-database.templates:4001 -msgid "New password for the ZoneMinder 'admin' user:" -msgstr "" - -#. Type: password -#. Description -#: ../zoneminder-database.templates:4001 -msgid "Please enter the password of the default administrative user." -msgstr "" - -#. Type: password -#. Description -#: ../zoneminder-database.templates:4001 -msgid "" -"While not mandatory, it is highly recommended that you set a custom password " -"for the administrative 'admin' user." -msgstr "" - -#. Type: password -#. Description -#: ../zoneminder-database.templates:4001 -msgid "If this field is left blank, the password will not be changed." -msgstr "" - -#. Type: password -#. Description -#: ../zoneminder-database.templates:5001 -msgid "Repeat password for the ZoneMinder 'admin' user:" -msgstr "" - -#. Type: error -#. Description -#: ../zoneminder-database.templates:6001 -msgid "Password input error" -msgstr "" - -#. Type: error -#. Description -#: ../zoneminder-database.templates:6001 -msgid "The two passwords you entered were not the same. Please try again." -msgstr "" - -#. Type: multiselect -#. Description -#: ../zoneminder-ui-base.templates:1001 -msgid "Web server to reconfigure automatically:" -msgstr "" - -#. Type: multiselect -#. Description -#: ../zoneminder-ui-base.templates:1001 -msgid "" -"Please choose the web server that should be automatically configured for " -"ZoneMinder's web portal access." -msgstr "" diff --git a/distros/ubuntu1410/rules b/distros/ubuntu1410/rules deleted file mode 100755 index 49d3549f1..000000000 --- a/distros/ubuntu1410/rules +++ /dev/null @@ -1,154 +0,0 @@ -#!/usr/bin/make -f -# -*- makefile -*- -# Sample debian/rules that uses debhelper. -# This file was originally written by Joey Hess and Craig Small. -# As a special exception, when this file is copied by dh-make into a -# dh-make output file, you may use that output file without restriction. -# This special exception was added by Craig Small in version 0.37 of dh-make. - -# Uncomment this to turn on verbose mode. -#export DH_VERBOSE=1 - -# These are used for cross-compiling and for saving the configure script -# from having to guess our platform (since we know it already) -DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) -DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) - -CFLAGS = -Wall -CPPFLAGS = -D__STDC_CONSTANT_MACROS -CXXFLAGS = -DHAVE_LIBCRYPTO - -ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS))) -DEBOPT = --enable-debug -CFLAGS += -g -CXXFLAGS += -g -else -DEBOPT = -endif - -ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) -CFLAGS += -O0 -else -CFLAGS += -O2 -endif - -# These are used to get the most recent version of the original sources from github -UURL = $(shell git config --get remote.origin.url) -BRANCH = $(shell git rev-parse --abbrev-ref HEAD) -HEAD = $(shell git rev-parse HEAD) -PKD = $(abspath $(dir $(MAKEFILE_LIST))) -PKG = $(word 2,$(shell dpkg-parsechangelog -l$(PKD)/changelog | grep ^Source)) -VER ?= $(shell dpkg-parsechangelog -l$(PKD)/changelog | perl -ne 'print $$1 if m{^Version:\s+(?:\d+:)?(\d.*)(?:\-|\+nmu\d+.*)};') -DTYPE = -TARBALL = ../$(PKG)_$(VER)$(DTYPE).orig.tar.xz - -%: - dh $@ --with autoreconf - -override_dh_auto_configure: - CFLAGS="$(CFLAGS)" CXXFLAGS="$(CXXFLAGS)" dh_auto_configure -- \ - --host=$(DEB_HOST_GNU_TYPE) --build=$(DEB_BUILD_GNU_TYPE) \ - --sysconfdir=/etc/zm --prefix=/usr --mandir=\$${prefix}/share/man \ - --infodir=\$${prefix}/share/info --with-mysql=/usr \ - --with-mariadb=/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 $(DEBOPT) - -override_dh_clean: - # Add here commands to clean up after the build process. - [ ! -f Makefile ] || $(MAKE) distclean - dh_clean src/zm_config_defines.h - # - # Delete remaining auto-generated Makefile if Makefile.in exists - find $(CURDIR)/ -type f -name "Makefile" | while read file; do \ - [ -f $$file.in ] && rm -f $$file; \ - done || true - # - # Delete remaining auto-generated Makefile.in if Makefile.am exists - find $(CURDIR)/ -type f -name "Makefile.in" | while read filein; do \ - fileam=`echo $$filein | sed 's/\(.*\)\.in/\1\.am/'`; \ - [ -f $$fileam ] && rm -f $$filein; \ - done || true - -override_dh_install: - dh_install --fail-missing - # - # NOTE: This is a short-term kludge; hopefully changes in the next - # upstream version will render this unnecessary. - rm -rf debian/zoneminder/usr/share/zoneminder/events - rm -rf debian/zoneminder/usr/share/zoneminder/images - rm -rf debian/zoneminder/usr/share/zoneminder/temp - # The link stuff for these folders has been moved to - # zoneminder-core.links file - # - # This is a slightly lesser kludge; moving the cgi stuff to - # /usr/share/zoneminder/cgi-bin breaks one set of behavior, - # having it just in /usr/lib/cgi-bin breaks another bit of - # behavior. - # The link stuff for /usr/share/zoneminder/cgi-bin has been moved to - # zoneminder-ui-base.links file - -override_dh_installinit: - dh_installinit --package=zoneminder-core --name=zoneminder - -override_dh_systemd_start: - dh_systemd_start --package=zoneminder-core --name=zoneminder \ - --restart-after-upgrade - -override_dh_systemd_enable: - dh_systemd_enable --package=zoneminder-core --name=zoneminder - -override_dh_fixperms: - dh_fixperms - # - # As requested by the Debian Webapps Policy Manual §3.2.1 - chown root:www-data debian/zoneminder-core/etc/zm/zm.conf - chmod 640 debian/zoneminder-core/etc/zm/zm.conf - -override_dh_auto_test: - # do not run tests... - -.PHONY: override_dh_strip -override_dh_strip: - dh_strip --dbg-package=zoneminder-core-dbg - -# Inspired by https://wiki.debian.org/onlyjob/get-orig-source -.PHONY: get-orig-source -get-orig-source: $(TARBALL) $(info I: $(PKG)_$(VER)$(DTYPE)) - @ - -$(TARBALL): - $(if $(wildcard $(PKG)-$(VER)),$(error folder '$(PKG)-$(VER)' exists, aborting...)) - @echo "# Cloning origin repository..."; \ - if ! git clone $(UURL) $(PKG)-$(VER); then \ - $(RM) -r $(PKG)-$(VER); \ - echo "failed to clone repository, aborting..."; \ - false; \ - fi - @if [ $(BRANCH) != "master" ]; then \ - cd $(PKG)-$(VER); \ - echo "# Not on master branch, fetching origin branch '$(BRANCH)'..."; \ - git fetch origin $(BRANCH):$(BRANCH) || false; \ - echo "# Switching to branch '$(BRANCH)'..."; \ - git checkout $(BRANCH) || false; \ - fi - @echo "# Checking local source..." - @if [ $$(cd $(PKG)-$(VER) && git rev-parse HEAD) = $(HEAD) ]; then \ - echo "even with origin, ok"; \ - true; \ - else \ - echo "not even with origin, aborting..."; \ - false; \ - fi - @echo "# Setting times..." - @cd $(PKG)-$(VER) \ - && for F in $$(git ls-tree -r --name-only HEAD | sed -e "s/\s/\*/g"); do \ - touch --no-dereference -d "$$(git log -1 --format="%ai" -- $$F)" "$$F"; \ - done - @echo "# Cleaning-up..." - cd $(PKG)-$(VER) && $(RM) -r .git - @echo "# Packing file '$(TARBALL)'..." - @find -L "$(PKG)-$(VER)" -xdev -type f -print | sort \ - | XZ_OPT="-6v" tar -caf "$(TARBALL)" -T- --owner=root --group=root --mode=a+rX \ - && $(RM) -r "$(PKG)-$(VER)" diff --git a/distros/ubuntu1410/source/format b/distros/ubuntu1410/source/format deleted file mode 100644 index 89ae9db8f..000000000 --- a/distros/ubuntu1410/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (native) diff --git a/distros/ubuntu1410/source/local-options b/distros/ubuntu1410/source/local-options deleted file mode 100644 index e69de29bb..000000000 diff --git a/distros/ubuntu1410/source/options b/distros/ubuntu1410/source/options deleted file mode 100644 index 8bd61fce6..000000000 --- a/distros/ubuntu1410/source/options +++ /dev/null @@ -1 +0,0 @@ -extend-diff-ignore = "(^|/)(config\.sub|config\.guess|Makefile|aclocal.m4|compile|config.h.in|configure|depcomp|install-sh|missing)$" diff --git a/distros/ubuntu1410/zoneminder-core.config b/distros/ubuntu1410/zoneminder-core.config deleted file mode 100644 index 2a15a599e..000000000 --- a/distros/ubuntu1410/zoneminder-core.config +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -# config maintainer script for zoneminder-core package - -set -e - -# Source the debconf stuff -. /usr/share/debconf/confmodule - -#DEBHELPER# - -exit 0 diff --git a/distros/ubuntu1410/zoneminder-core.dirs b/distros/ubuntu1410/zoneminder-core.dirs deleted file mode 100644 index 350c32aff..000000000 --- a/distros/ubuntu1410/zoneminder-core.dirs +++ /dev/null @@ -1,4 +0,0 @@ -var/log/zm -var/cache/zoneminder/events -var/cache/zoneminder/images -var/cache/zoneminder/temp diff --git a/distros/ubuntu1410/zoneminder-core.install b/distros/ubuntu1410/zoneminder-core.install deleted file mode 100644 index afd9ada95..000000000 --- a/distros/ubuntu1410/zoneminder-core.install +++ /dev/null @@ -1,4 +0,0 @@ -etc/zm -usr/bin -usr/share/polkit-1/actions -usr/share/polkit-1/rules.d diff --git a/distros/ubuntu1410/zoneminder-core.links b/distros/ubuntu1410/zoneminder-core.links deleted file mode 100644 index 5560a100a..000000000 --- a/distros/ubuntu1410/zoneminder-core.links +++ /dev/null @@ -1,3 +0,0 @@ -var/cache/zoneminder/events usr/share/zoneminder/events -var/cache/zoneminder/images usr/share/zoneminder/images -var/cache/zoneminder/temp usr/share/zoneminder/temp diff --git a/distros/ubuntu1410/zoneminder-core.postinst b/distros/ubuntu1410/zoneminder-core.postinst deleted file mode 100644 index da2b444fe..000000000 --- a/distros/ubuntu1410/zoneminder-core.postinst +++ /dev/null @@ -1,80 +0,0 @@ -#! /bin/sh -# postinst maintainer script for zoneminder-core package - -set -e - -# Source the debconf stuff -. /usr/share/debconf/confmodule - -# Source the config file -CONFIGFILE=/etc/zm/zm.conf -. $CONFIGFILE - -# Do this when the package is installed, upgraded or reconfigured -if [ "$1" = "configure" ] || [ "$1" = "reconfigure" ]; then - - # Retrieve data from dbconfig (inputs from user) - . /etc/dbconfig-common/zoneminder.conf - - # ZoneMinder config file handling - # Inspired by: http://manpages.debian.org/cgi-bin/man.cgi?query=debconf-devel&sektion=7 - - # Backup the config file - cp -a -f $CONFIGFILE ${CONFIGFILE}.postinst.bak - - # Redeclare variables if missing in config file - test -z $dbc_dbserver || grep -Eq "^ *ZM_DB_HOST=" $CONFIGFILE \ - || echo "ZM_DB_HOST=" >> ${CONFIGFILE}.postinst.bak - test -z $dbc_dbname || grep -Eq "^ *ZM_DB_NAME=" $CONFIGFILE \ - || echo "ZM_DB_NAME=" >> ${CONFIGFILE}.postinst.bak - test -z $dbc_dbuser || grep -Eq "^ *ZM_DB_USER=" $CONFIGFILE \ - || echo "ZM_DB_USER=" >> ${CONFIGFILE}.postinst.bak - test -z $dbc_dbpass || grep -Eq "^ *ZM_DB_PASS=" $CONFIGFILE \ - || echo "ZM_DB_PASS=" >> ${CONFIGFILE}.postinst.bak - - # Prevent ZM_DB_HOST to be empty if user selected the 'unix socket' method - if test -z $dbc_dbserver; then - dbc_dbserver_override="localhost" - else - dbc_dbserver_override=$dbc_dbserver - fi - - # Update variables in config file - sed -i "s/^ *ZM_DB_HOST=.*/ZM_DB_HOST=$dbc_dbserver_override/" \ - ${CONFIGFILE}.postinst.bak - test -z $dbc_dbname || sed -i "s/^ *ZM_DB_NAME=.*/ZM_DB_NAME=$dbc_dbname/" \ - ${CONFIGFILE}.postinst.bak - test -z $dbc_dbuser || sed -i "s/^ *ZM_DB_USER=.*/ZM_DB_USER=$dbc_dbuser/" \ - ${CONFIGFILE}.postinst.bak - test -z $dbc_dbpass || sed -i "s/^ *ZM_DB_PASS=.*/ZM_DB_PASS=$dbc_dbpass/" \ - ${CONFIGFILE}.postinst.bak - - # Clean-up backup file - mv -f ${CONFIGFILE}.postinst.bak $CONFIGFILE - - - # Set some file permissions - chown $ZM_WEB_USER:$ZM_WEB_GROUP /var/log/zm - if [ -z "$2" ]; then - chown $ZM_WEB_USER:$ZM_WEB_GROUP -R /var/cache/zoneminder - fi - # As requested by the Debian Webapps Policy Manual §3.2.1 - chown root:${ZM_WEB_GROUP} $CONFIGFILE - chmod 640 $CONFIGFILE -fi - -# Do this every time the package is installed or upgraded -# Test for database presence to avoid failure of zmupdate.pl -if [ "$dbc_install" = "true" ] && [ "$1" = "configure" ]; then - - # Ensure zoneminder is stopped - deb-systemd-invoke stop zoneminder.service || exit $? - - # Run the ZoneMinder update tool - zmupdate.pl --nointeractive - -fi - -#DEBHELPER# - -exit 0 diff --git a/distros/ubuntu1410/zoneminder-core.postrm b/distros/ubuntu1410/zoneminder-core.postrm deleted file mode 100644 index d75e75e8b..000000000 --- a/distros/ubuntu1410/zoneminder-core.postrm +++ /dev/null @@ -1,37 +0,0 @@ -#! /bin/sh -# postrm maintainer script for zoneminder-core package - -set -e - -# Source the debconf stuff -if [ -f /usr/share/debconf/confmodule ]; then - . /usr/share/debconf/confmodule -fi - -if [ "$1" = "purge" ]; then - - # Ask the user if we have to remove the cache directory even if not empty - if [ -d /var/cache/zoneminder ] \ - && [ ! $(find /var/cache/zoneminder -maxdepth 0 -type d -empty 2>/dev/null) ]; then - RET="" - db_input high zoneminder/ask_delete || true - db_go || true - db_get zoneminder/ask_delete - if [ "$RET" = "true" ]; then - RET="" - db_input high zoneminder/ask_delete_again || true - db_go || true - db_get zoneminder/ask_delete_again - if [ "$RET" = "true" ]; then - rm -rf /var/cache/zoneminder - fi - fi - fi -fi - -#DEBHELPER# - -# postrm rm may freeze without that -db_stop - -exit 0 diff --git a/distros/ubuntu1410/zoneminder-core.preinst b/distros/ubuntu1410/zoneminder-core.preinst deleted file mode 100644 index 3ed1ef661..000000000 --- a/distros/ubuntu1410/zoneminder-core.preinst +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh -# preinst maintainer script for zoneminder-core package - -set -e - -abort=false -if [ -L /usr/share/zoneminder/events ]; then - l=$(readlink /usr/share/zoneminder/events) - if [ "$l" != "/var/cache/zoneminder/events" ]; then - abort=true - fi -fi -if [ -L /usr/share/zoneminder/images ]; then - l=$(readlink /usr/share/zoneminder/images ) - if [ "$l" != "/var/cache/zoneminder/images" ]; then - abort=true - fi -fi - -if [ "$abort" = "true" ]; then - cat >&2 << EOF -Aborting installation of zoneminder due to non-default symlinks in -/usr/share/zoneminder for the images and/or events directory, which could -result in loss of data. Please move your data in each of these directories to -/var/cache/zoneminder before installing zoneminder from the package. -EOF - exit 1 - -fi - -#DEBHELPER# - -exit 0 diff --git a/distros/ubuntu1410/zoneminder-core.templates b/distros/ubuntu1410/zoneminder-core.templates deleted file mode 100644 index 35fdefd7a..000000000 --- a/distros/ubuntu1410/zoneminder-core.templates +++ /dev/null @@ -1,19 +0,0 @@ -Template: zoneminder/ask_delete -Type: boolean -Default: false -_Description: Delete this non empty directory? - A purge of the ZoneMinder package is performed but the directory - '/var/cache/zoneminder' is not empty so it will not be deleted. - . - Please consider that this directory is designed to contain data resulting from - event detection. Therefore, "proof of evidence" could be lost!" - . - If you are not sure of your decision, please do not delete this directory but - perform a manual checkup. - -Template: zoneminder/ask_delete_again -Type: boolean -Default: false -_Description: Deletion confirmed? - You have allowed the deletion of directory '/var/cache/zoneminder' although - it may contain critical data. diff --git a/distros/ubuntu1410/zoneminder-core.zoneminder.service b/distros/ubuntu1410/zoneminder-core.zoneminder.service deleted file mode 100644 index d82270024..000000000 --- a/distros/ubuntu1410/zoneminder-core.zoneminder.service +++ /dev/null @@ -1,19 +0,0 @@ -# ZoneMinder systemd unit file -# This file is intended to work with debian distributions - -[Unit] -Description=ZoneMinder CCTV recording and security system -After=network.target mysql.service apache2.service -Requires=apache2.service -Wants=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=/var/run/zm/zm.pid - -[Install] -WantedBy=multi-user.target diff --git a/distros/ubuntu1410/zoneminder-core.zoneminder.tmpfile b/distros/ubuntu1410/zoneminder-core.zoneminder.tmpfile deleted file mode 100644 index 6ea70bf35..000000000 --- a/distros/ubuntu1410/zoneminder-core.zoneminder.tmpfile +++ /dev/null @@ -1 +0,0 @@ -d /var/run/zm 0755 www-data www-data diff --git a/distros/ubuntu1410/zoneminder-database.config b/distros/ubuntu1410/zoneminder-database.config deleted file mode 100644 index f6a84d36d..000000000 --- a/distros/ubuntu1410/zoneminder-database.config +++ /dev/null @@ -1,142 +0,0 @@ -#!/bin/sh -# config maintainer script for zoneminder - -set -e - -# Source the debconf stuff -. /usr/share/debconf/confmodule - -# Set the first version in which dbconfig-common was introduced in the package -dbc_first_version="1.28.0" - -CONFIGFILE=/etc/zm/zm.conf -if [ -e $CONFIGFILE ]; then - # Source the config file if exists - . $CONFIGFILE -elif [ -e ${CONFIGFILE}.dpkg-new ]; then - # If no config file, source the config file which is going to be installed - # by the core package - . ${CONFIGFILE}.dpkg-new -else - # If no config file is going to be installed, set some default values - ZM_DB_HOST= - ZM_DB_NAME="zm" - ZM_DB_USER="zmuser" -fi - -# Set some variables for the dbconfig-common stuff -dbc_dbserver="$ZM_DB_HOST" -dbc_dbname="$ZM_DB_NAME" -dbc_dbuser="$ZM_DB_USER" - -if [ -f /usr/share/dbconfig-common/dpkg/config ]; then - - # Default use dbconfig-common - dbc_install="true" - - # Currently we only support mysql database - dbc_dbtypes="mysql" - - # Set authentication method to password - dbc_authmethod_user="password" - - # Source the dbconfig-common stuff - . /usr/share/dbconfig-common/dpkg/config -fi - -# Do this when the package is installed, upgraded or reconfigured -# Most of answers are cached so the questions will not be asked again -if [ "$1" = "configure" ] || [ "$1" = "reconfigure" ]; then - - # Ask the user if the database shall be installed locally or remotely - db_input high zoneminder/debconf_dblocation || true - db_go || true - db_get zoneminder/debconf_dblocation - - if [ "$RET" = "local" ]; then - if [ ! -e "/usr/sbin/mysqld" ]; then - # Display a message and exit if the user want a local database but - # no database server is available - db_input high zoneminder/debconf_dblocalmissingerror || true - db_go || true - exit 0 - else - # Set the database server to localhost - dbc_dbserver="localhost" - fi - else - # Source the dbconfig main configuration file - if [ -f /etc/dbconfig-common/config ]; then - . /etc/dbconfig-common/config - fi - if [ "$dbc_remote_questions_default" = "false" ]; then - # Display a message and exit if the dbconfig configuration does not - # allow installation of remote databases - # Note: To overcome this issue, we could think to override the - # default setting by using dbc_remote_questions_default='true' in - # maintainer scripts but unfortunately this does not work due to - # current dbconfig design - # More information here: - # https://bugs.launchpad.net/ubuntu/+source/dbconfig-common/+bug/1065331 - db_input high zoneminder/debconf_dbconfigerror || true - db_go || true - exit 0 - fi - fi - - # Ask the user for all database settings - dbc_go zoneminder $@ - - # Ask the user for the password of the database administrator if the user - # has not yet answered to this question. - # This situation may occur if the user skipped the database creation step - # when reconfiguring the package. - RET="" - db_get zoneminder/mysql/admin-pass - if [ -z "$RET" ]; then - db_input high zoneminder/mysql/admin-pass || true - db_go || true - fi - - # Do this only when not upgrading the package (no old version in argument) - if [ -z "$2" ]; then - # Ask for the password of 'admin' user - while :; do - RET="" - db_input high zoneminder/admin_password || true - db_go || true - db_get zoneminder/admin_password - # If password isn't empty we ask for password verification - if [ -z "$RET" ]; then - db_fset zoneminder/admin_password seen false - db_fset zoneminder/admin_password_again seen false - break - fi - ROOT_PW="$RET" - db_input high zoneminder/admin_password_again || true - db_go || true - db_get zoneminder/admin_password_again - if [ "$RET" = "$ROOT_PW" ]; then - ROOT_PW="" - break - fi - db_fset zoneminder/password_mismatch seen false - db_input critical zoneminder/password_mismatch || true - db_set zoneminder/admin_password "" - db_set zoneminder/admin_password_again "" - db_go || true - done - else - # If we are upgrading the package, set an empty password to disable - # password update in ZoneMinder database - db_set zoneminder/admin_password "" - fi - # Set the seen flag to not ask this question again if no password is - # provided - db_fset zoneminder/admin_password seen true - -fi - -#DEBHELPER# - -exit 0 diff --git a/distros/ubuntu1410/zoneminder-database.dirs b/distros/ubuntu1410/zoneminder-database.dirs deleted file mode 100644 index b37463a9e..000000000 --- a/distros/ubuntu1410/zoneminder-database.dirs +++ /dev/null @@ -1,3 +0,0 @@ -usr/share/zoneminder/db -usr/share/dbconfig-common/data/zoneminder/install -usr/share/dbconfig-common/data/zoneminder/upgrade/mysql diff --git a/distros/ubuntu1410/zoneminder-database.install b/distros/ubuntu1410/zoneminder-database.install deleted file mode 100644 index 756c5bbfa..000000000 --- a/distros/ubuntu1410/zoneminder-database.install +++ /dev/null @@ -1 +0,0 @@ -usr/share/zoneminder/db diff --git a/distros/ubuntu1410/zoneminder-database.postinst b/distros/ubuntu1410/zoneminder-database.postinst deleted file mode 100644 index 41d4e5b5b..000000000 --- a/distros/ubuntu1410/zoneminder-database.postinst +++ /dev/null @@ -1,79 +0,0 @@ -#! /bin/sh -# postinst maintainer script for zoneminder-db package - -set -e - -# Source the debconf stuff -. /usr/share/debconf/confmodule - -mysql_update() { - - # Source the dbconfig stuff - . /usr/share/dbconfig-common/internal/mysql - - # Update the password of the hard-coded default 'admin' account - test -z $ADMIN_PASSWORD || dbc_mysql_exec_command "UPDATE Users SET Password = password('$ADMIN_PASSWORD') WHERE Username = 'admin';" || true - - # Update the database version - dbc_mysql_exec_command "UPDATE Config SET Value = '$DB_VERSION' WHERE Name = 'ZM_DYN_DB_VERSION';" || true -} - -if [ -f /usr/share/dbconfig-common/dpkg/postinst ]; then - - # Set the first version in which dbconfig-common was introduced in the package - dbc_first_version="1.28.0" - - # Set the database type - dbc_dbtypes="mysql" - - # Source the dbconfig-common stuff - . /usr/share/dbconfig-common/dpkg/postinst -fi - -# Do this when the package is installed, upgraded or reconfigured -if [ "$1" = "configure" ] || [ "$1" = "reconfigure" ]; then - - # Install sql database create file for dbconfig - # (needed at first package installation) - if [ ! -f /usr/share/dbconfig-common/data/zoneminder/install/mysql ]; then - install -m 644 /usr/share/zoneminder/db/zm_create.sql \ - /usr/share/dbconfig-common/data/zoneminder/install/mysql - # Remove unneeded sql requests - # dbconfig will create the underlying database - sed -i "/^ *CREATE DATABASE /d" \ - /usr/share/dbconfig-common/data/zoneminder/install/mysql - sed -i "/^ *USE /d" \ - /usr/share/dbconfig-common/data/zoneminder/install/mysql - fi - - # Symlink sql update files for dbconfig (needed when upgrading the package) - for sqlfile in /usr/share/zoneminder/db/zm_update-*.sql; do - lnk=`echo $sqlfile | sed "s/^\/usr\/share\/zoneminder\/db\/zm_update-\(.*\)\.sql/\1/"` - if [ ! -L /usr/share/dbconfig-common/data/zoneminder/upgrade/mysql/$lnk ]; then - ln -sf $sqlfile \ - /usr/share/dbconfig-common/data/zoneminder/upgrade/mysql/$lnk - fi - done || true - - # Create the underlying database and populate it - # dbconfig will take care of applying any updates which are newer than the - # previously installed version - dbc_go zoneminder $@ - - # Get the password of ZoneMinder user 'admin' from debconf - db_get zoneminder/admin_password - ADMIN_PASSWORD=$RET - - # Remove the password from debconf database - test -z $ADMIN_PASSWORD || db_reset zoneminder/admin_password || true - - # Get the lastest database version from dbconfig upgrade folder - DB_VERSION=$(ls -rv /usr/share/dbconfig-common/data/zoneminder/upgrade/$dbc_dbtypes | head -1) - - # Update the default admin account and database version - mysql_update -fi - -#DEBHELPER# - -exit 0 diff --git a/distros/ubuntu1410/zoneminder-database.postrm b/distros/ubuntu1410/zoneminder-database.postrm deleted file mode 100644 index 231f01ad7..000000000 --- a/distros/ubuntu1410/zoneminder-database.postrm +++ /dev/null @@ -1,34 +0,0 @@ -#! /bin/sh -# postrm maintainer script for zoneminder-db package - -set -e - -# Source the debconf stuff -if [ -f /usr/share/debconf/confmodule ]; then - . /usr/share/debconf/confmodule -fi - -# Source the dbconfig stuff -if [ -f /usr/share/dbconfig-common/dpkg/postrm ]; then - . /usr/share/dbconfig-common/dpkg/postrm - # Ask the user what do to with dbconfig when removing the package - dbc_go zoneminder $@ -fi - -if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then - # Remove dbconfig stuff added in postinst script - rm -rf /usr/share/dbconfig-common/data/zoneminder - # No need to manually remove the zm database, dbconfig take care of this -fi - -if [ "$1" = "purge" ]; then - # Delete a potential remaining file used in postinst script - rm -f /etc/zm/zm.conf.postinst.bak -fi - -#DEBHELPER# - -# postrm rm may freeze without that -db_stop - -exit 0 diff --git a/distros/ubuntu1410/zoneminder-database.prerm b/distros/ubuntu1410/zoneminder-database.prerm deleted file mode 100644 index 31786116a..000000000 --- a/distros/ubuntu1410/zoneminder-database.prerm +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh -# prerm script for zoneminder-db package - -set -e - -# Source the debconf stuff if file exists -if [ -f /usr/share/debconf/confmodule ]; then - . /usr/share/debconf/confmodule -fi - -# If dbconfig-common is installed and has been used by zoneminder -if [ -f /usr/share/dbconfig-common/dpkg/prerm ] \ - && [ -f /etc/dbconfig-common/zoneminder.conf ]; then - # Source the dbconfig stuff - . /usr/share/dbconfig-common/dpkg/prerm - # Ask the user what do to with dbconfig before removing the package - dbc_go zoneminder $@ -fi - -# #DEBHELPER# - -exit 0 diff --git a/distros/ubuntu1410/zoneminder-database.templates b/distros/ubuntu1410/zoneminder-database.templates deleted file mode 100644 index 4de4342f6..000000000 --- a/distros/ubuntu1410/zoneminder-database.templates +++ /dev/null @@ -1,58 +0,0 @@ -Template: zoneminder/debconf_dblocation -Type: select -__Choices: local, remote -Default: local -_Description: Database location: - A database server is required to run ZoneMinder. The database can be installed - either locally or remotely on a machine of your network. - . - If you choose a remote location, you will have to select the 'tcp/ip' - connection method and enter the hostname or ip address of the remote machine - in the next configuration screens. - -Template: zoneminder/debconf_dblocalmissingerror -Type: error -_Description: No local database server is available: - Currently ZoneMinder supports mysql or mariadb database server but none of them - appears to be installed on this machine. - . - In order to complete ZoneMinder's installation, after ending of this assistant, - please install a compatible database server and then restart the assistant by - invoking: - . - $ sudo dpkg-reconfigure zoneminder - -Template: zoneminder/debconf_dbconfigerror -Type: error -_Description: Remote database servers are not allowed: - The current configuration of dbconfig-common does not allow installation of - a database on remote servers. - . - In order to reconfigure dbconfig-common, please invoke the following command - after ending of this assistant: - . - $ sudo dpkg-reconfigure dbconfig-common - . - Then, to complete ZoneMinder's installation, please restart this assistant by - invoking: - . - $ sudo dpkg-reconfigure zoneminder - -Template: zoneminder/admin_password -Type: password -_Description: New password for the ZoneMinder 'admin' user: - Please enter the password of the default administrative user. - . - While not mandatory, it is highly recommended that you set a custom password - for the administrative 'admin' user. - . - If this field is left blank, the password will not be changed. - -Template: zoneminder/admin_password_again -Type: password -_Description: Repeat password for the ZoneMinder 'admin' user: - -Template: zoneminder/password_mismatch -Type: error -_Description: Password input error - The two passwords you entered were not the same. Please try again. diff --git a/distros/ubuntu1410/zoneminder-ui-base.config b/distros/ubuntu1410/zoneminder-ui-base.config deleted file mode 100644 index 2660208a8..000000000 --- a/distros/ubuntu1410/zoneminder-ui-base.config +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh -# config maintainer script for zoneminder-ui-base package - -set -e - -# Source the debconf stuff -. /usr/share/debconf/confmodule - -# Do this when the package is installed, upgraded or reconfigured -# Most of answers are cached so the questions will not be asked again -if [ "$1" = "configure" ] || [ "$1" = "reconfigure" ]; then - - # Ask the user for the web server(s) to configure - db_input high zoneminder/webserver || true - db_go || true -fi - -#DEBHELPER# - -exit 0 diff --git a/distros/ubuntu1410/zoneminder-ui-base.install b/distros/ubuntu1410/zoneminder-ui-base.install deleted file mode 100644 index f72b569be..000000000 --- a/distros/ubuntu1410/zoneminder-ui-base.install +++ /dev/null @@ -1,11 +0,0 @@ -debian/apache.conf etc/zm -usr/lib/cgi-bin -usr/share/zoneminder/ajax -usr/share/zoneminder/css -usr/share/zoneminder/graphics -usr/share/zoneminder/includes -usr/share/zoneminder/index.php -usr/share/zoneminder/js -usr/share/zoneminder/lang -usr/share/zoneminder/tools -usr/share/zoneminder/views diff --git a/distros/ubuntu1410/zoneminder-ui-base.links b/distros/ubuntu1410/zoneminder-ui-base.links deleted file mode 100644 index b00a147d6..000000000 --- a/distros/ubuntu1410/zoneminder-ui-base.links +++ /dev/null @@ -1 +0,0 @@ -usr/lib/cgi-bin usr/share/zoneminder/cgi-bin diff --git a/distros/ubuntu1410/zoneminder-ui-base.postinst b/distros/ubuntu1410/zoneminder-ui-base.postinst deleted file mode 100644 index a5bce3c98..000000000 --- a/distros/ubuntu1410/zoneminder-ui-base.postinst +++ /dev/null @@ -1,48 +0,0 @@ -#! /bin/sh -# postinst maintainer script for zoneminder-ui-base package - -set -e - -# Source the debconf stuff -. /usr/share/debconf/confmodule - -apache_install() { - - mkdir -p /etc/apache2/conf-available - ln -sf ../../zm/apache.conf /etc/apache2/conf-available/zoneminder.conf - - COMMON_STATE=$(dpkg-query -f '${Status}' -W 'apache2.2-common' 2>/dev/null | awk '{print $3}' || true) - - if [ -e /usr/share/apache2/apache2-maintscript-helper ] ; then - . /usr/share/apache2/apache2-maintscript-helper - apache2_invoke enconf zoneminder - elif [ "$COMMON_STATE" = "installed" ] || [ "$COMMON_STATE" = "unpacked" ] ; then - [ -d /etc/apache2/conf.d/ ] && [ ! -L /etc/apache2/conf.d/zoneminder.conf ] && ln -s ../conf-available/zoneminder.conf /etc/apache2/conf.d/zoneminder.conf - fi - - # Enable CGI script module in apache (not enabled by default on jessie) - a2enmod cgi >/dev/null 2>&1 - - # Reload the web server - deb-systemd-invoke reload apache2.service || true -} - -# Do this when the package is installed, upgraded or reconfigured -if [ "$1" = "configure" ] || [ "$1" = "reconfigure" ]; then - - # Configure the web server - db_get zoneminder/webserver - webservers="$RET" - - for webserver in $webservers; do - webserver=${webserver%,} - # Currently we only support apache2 - if [ "$webserver" = "apache2" ] ; then - apache_install $1 - fi - done -fi - -#DEBHELPER# - -exit 0 diff --git a/distros/ubuntu1410/zoneminder-ui-base.postrm b/distros/ubuntu1410/zoneminder-ui-base.postrm deleted file mode 100644 index 441bb5218..000000000 --- a/distros/ubuntu1410/zoneminder-ui-base.postrm +++ /dev/null @@ -1,41 +0,0 @@ -#! /bin/sh -# postrm maintainer script for zoneminder-ui-base package - -set -e - -# Source the debconf stuff -if [ -f /usr/share/debconf/confmodule ]; then - . /usr/share/debconf/confmodule -fi - -apache_remove() { - COMMON_STATE=$(dpkg-query -f '${Status}' -W 'apache2.2-common' 2>/dev/null | awk '{print $3}' || true) - if [ -e /usr/share/apache2/apache2-maintscript-helper ] ; then - . /usr/share/apache2/apache2-maintscript-helper - apache2_invoke disconf zoneminder - elif [ "$COMMON_STATE" = "installed" ] || [ "$COMMON_STATE" = "unpacked" ] ; then - rm -f /etc/apache2/conf.d/zoneminder.conf - fi - rm -f /etc/apache2/conf-available/zoneminder.conf - # Reload the web server - deb-systemd-invoke reload apache2.service || true -} - -if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then - # Deconfigure the web server - db_get zoneminder/webserver - for webserver in $RET; do - webserver=${webserver%,} - # Currently we only support apache2 - if [ "$webserver" = "apache2" ] ; then - apache_remove $1 - fi - done -fi - -#DEBHELPER# - -# postrm rm may freeze without that -db_stop - -exit 0 diff --git a/distros/ubuntu1410/zoneminder-ui-base.templates b/distros/ubuntu1410/zoneminder-ui-base.templates deleted file mode 100644 index 31e70277f..000000000 --- a/distros/ubuntu1410/zoneminder-ui-base.templates +++ /dev/null @@ -1,7 +0,0 @@ -Template: zoneminder/webserver -Type: multiselect -Choices: apache2 -Default: apache2 -_Description: Web server to reconfigure automatically: - Please choose the web server that should be automatically configured for - ZoneMinder's web portal access. diff --git a/distros/ubuntu1410/zoneminder-ui-classic.install b/distros/ubuntu1410/zoneminder-ui-classic.install deleted file mode 100644 index 9532d9dc9..000000000 --- a/distros/ubuntu1410/zoneminder-ui-classic.install +++ /dev/null @@ -1 +0,0 @@ -usr/share/zoneminder/skins/classic diff --git a/distros/ubuntu1410/zoneminder-ui-mobile.install b/distros/ubuntu1410/zoneminder-ui-mobile.install deleted file mode 100644 index 464bb74eb..000000000 --- a/distros/ubuntu1410/zoneminder-ui-mobile.install +++ /dev/null @@ -1 +0,0 @@ -usr/share/zoneminder/skins/mobile diff --git a/distros/ubuntu1410/zoneminder-ui-xml.install b/distros/ubuntu1410/zoneminder-ui-xml.install deleted file mode 100644 index 6617707f8..000000000 --- a/distros/ubuntu1410/zoneminder-ui-xml.install +++ /dev/null @@ -1 +0,0 @@ -usr/share/zoneminder/skins/xml diff --git a/distros/ubuntu1504_cmake_split_packages/control b/distros/ubuntu1504_cmake_split_packages/control index b24d67cf2..1345c39ee 100644 --- a/distros/ubuntu1504_cmake_split_packages/control +++ b/distros/ubuntu1504_cmake_split_packages/control @@ -8,15 +8,15 @@ 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 -, libdbi-perl, libnet-sftp-foreign-perl, libexpect-perl, libmime-tools-perl, libx264-dev, libmp4v2-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/ @@ -75,7 +75,7 @@ Depends: libzoneminder-perl (= ${source:Version}), libdbi-perl, libmodule-load-conditional-perl, libmime-lite-perl, libmime-tools-perl, libnet-sftp-foreign-perl, libphp-serialization-perl, debconf, ffmpeg | libav-tools, rsyslog | system-log-daemon, zip, - policykit-1, apache2, libmp4v2-2, libpcre++0 + policykit-1, apache2, libpcre++0 , libsys-cpu-perl, libsys-meminfo-perl , libdata-dump-perl, libclass-std-fast-perl, libsoap-wsdl-perl, libio-socket-multicast-perl, libdigest-sha-perl Description: Core binaries and perl scripts for ZoneMinder diff --git a/distros/ubuntu1504_cmake_split_packages/rules b/distros/ubuntu1504_cmake_split_packages/rules index f6169c495..218ab3c17 100755 --- a/distros/ubuntu1504_cmake_split_packages/rules +++ b/distros/ubuntu1504_cmake_split_packages/rules @@ -15,8 +15,6 @@ DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) CFLAGS = -Wall -CPPFLAGS = -D__STDC_CONSTANT_MACROS -CXXFLAGS = -DHAVE_LIBCRYPTO ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS))) DEBOPT = --enable-debug @@ -73,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/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/patches/series b/distros/ubuntu1604/patches/series deleted file mode 100644 index e69de29bb..000000000 diff --git a/distros/ubuntu1604/source/lintian-overrides b/distros/ubuntu1604/source/lintian-overrides deleted file mode 100644 index 3669e5de8..000000000 --- a/distros/ubuntu1604/source/lintian-overrides +++ /dev/null @@ -1,9 +0,0 @@ -## Actually sources are there: "*-nc.js". -source-is-missing web/tools/mootools/mootools-*-yc.js - -## We're using "libjs-jquery" instead. -source-is-missing web/skins/*/js/jquery-1.4.2.min.js - -## Acknowledged, will repack eventually. -source-contains-prebuilt-javascript-object web/tools/mootools/mootools-*-yc.js -source-contains-prebuilt-javascript-object web/skins/*/js/jquery-1.4.2.min.js diff --git a/distros/ubuntu1604/zoneminder.linktrees b/distros/ubuntu1604/zoneminder.linktrees deleted file mode 100644 index 2e843bbf1..000000000 --- a/distros/ubuntu1604/zoneminder.linktrees +++ /dev/null @@ -1,14 +0,0 @@ -## cakephp -#replace /usr/share/php/Cake /usr/share/zoneminder/www/api/lib/Cake - -## libjs-mootools -replace /usr/share/javascript/mootools/mootools.js /usr/share/zoneminder/www/tools/mootools/mootools-core.js -replace /usr/share/javascript/mootools/mootools.js /usr/share/zoneminder/www/tools/mootools/mootools-core-1.3.2-nc.js -replace /usr/share/javascript/mootools/mootools.js /usr/share/zoneminder/www/tools/mootools/mootools-core-1.3.2-yc.js -replace /usr/share/javascript/mootools/mootools-more.js /usr/share/zoneminder/www/tools/mootools/mootools-more.js -replace /usr/share/javascript/mootools/mootools-more.js /usr/share/zoneminder/www/tools/mootools/mootools-more-1.3.2.1-nc.js -replace /usr/share/javascript/mootools/mootools-more.js /usr/share/zoneminder/www/tools/mootools/mootools-more-1.3.2.1-yc.js - -## libjs-jquery -replace /usr/share/javascript/jquery/jquery.min.js /usr/share/zoneminder/www/skins/classic/js/jquery-1.4.2.min.js -replace /usr/share/javascript/jquery/jquery.min.js /usr/share/zoneminder/www/skins/flat/js/jquery-1.4.2.min.js diff --git a/distros/ubuntu1604/zoneminder.postinst b/distros/ubuntu1604/zoneminder.postinst deleted file mode 100644 index d3983950b..000000000 --- a/distros/ubuntu1604/zoneminder.postinst +++ /dev/null @@ -1,90 +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 - if [ -z "$2" ]; then - chown www-data:www-data /var/cache/zoneminder /var/cache/zoneminder/* - fi - 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 [ "$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 "grant lock tables,alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql - else - echo "Updating permissions" - echo "grant lock tables,alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost;" | mysql --defaults-file=/etc/mysql/debian.cnf mysql - fi - - 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 - deb-systemd-invoke restart zoneminder.service - -fi - -#DEBHELPER# diff --git a/distros/ubuntu2004/NEWS b/distros/ubuntu2004/NEWS new file mode 100644 index 000000000..6200726cf --- /dev/null +++ b/distros/ubuntu2004/NEWS @@ -0,0 +1,10 @@ +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/ubuntu1204/README.Debian b/distros/ubuntu2004/README.Debian similarity index 64% rename from distros/ubuntu1204/README.Debian rename to distros/ubuntu2004/README.Debian index 2ba809fe4..4fe3464d2 100644 --- a/distros/ubuntu1204/README.Debian +++ b/distros/ubuntu2004/README.Debian @@ -8,7 +8,7 @@ Initializing database OR 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 on zm.* to 'zmuser'@localhost identified by "zmpass";'\ + 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" @@ -23,8 +23,7 @@ configuration file: Upgrading database ------------------ -Prior to 1.28.1 database upgrade was performed automatically. -"zoneminder" service will refuse to start with outdated 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: @@ -45,17 +44,11 @@ The following command prints the current version of zoneminder database: Enabling service ---------------- -By default Zoneminder service is not starting automatically and need to be -manually activated once database is configured: - -On systemd: +By default Zoneminder service is not automatically started and needs to be +manually enabled once database is configured: sudo systemctl enable zoneminder.service -On SysV: - - sudo update-rc.d zoneminder enable - Web server set-up ----------------- @@ -82,10 +75,10 @@ Common configuration steps for Apache2: ## nginx / fcgiwrap -Nginx needs "php5-fpm" package to support PHP and "fcgiwrap" package +Nginx needs "php-fpm" package to support PHP and "fcgiwrap" package for binary "cgi-bin" applications: - sudo apt-get install php5-fpm fcgiwrap + sudo apt-get install php-fpm fcgiwrap To enable a URL alias that makes Zoneminder available from @@ -119,32 +112,9 @@ site configuration. Changing the location for images and events ------------------------------------------- -Zoneminder, in its upstream form, stores data in /usr/share/zoneminder/. This -package modifies that by changing /usr/share/zoneminder/images and -/usr/share/zoneminder/events to symlinks to directories under -/var/cache/zoneminder. - -There are numerous places these could be put and ways to do it. But, at the -moment, if you change this, an upgrade will fail with a warning about these -locations having changed (the reason for this was that previously, an upgrade -would silently revert the changes and cause event loss - refer -bug #608793). - -If you do want to change the location, here are a couple of suggestions. -(thanks to vagrant@freegeek.org): - -These lines in fstab could allow you to bind-mount an alternate location - - /dev/sdX1 /otherdrive ext3 defaults 0 2 - /otherdrive/zoneminder/images /var/cache/zoneminder/images bind defaults 0 2 - /otherdrive/zoneminder/events /var/cache/zoneminder/events bind defaults 0 2 - - or if you have a separate partition for each: - - /dev/sdX1 /var/cache/zoneminder/images ext3 defaults 0 2 - /dev/sdX2 /var/cache/zoneminder/events ext3 defaults 0 2 - - -- Peter Howard , Sun, 16 Jan 2010 01:35:51 +1100 +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* --------------------- diff --git a/distros/ubuntu1604/TODO.Debian b/distros/ubuntu2004/TODO similarity index 100% rename from distros/ubuntu1604/TODO.Debian rename to distros/ubuntu2004/TODO diff --git a/distros/ubuntu2004/changelog b/distros/ubuntu2004/changelog new file mode 100644 index 000000000..616f75178 --- /dev/null +++ b/distros/ubuntu2004/changelog @@ -0,0 +1,3 @@ +zoneminder (1.31.39~20180223.27-stretch-1) unstable; urgency=low + * + -- Isaac Connor Fri, 23 Feb 2018 14:15:59 -0500 diff --git a/distros/ubuntu1604/clean b/distros/ubuntu2004/clean similarity index 100% rename from distros/ubuntu1604/clean rename to distros/ubuntu2004/clean diff --git a/distros/ubuntu2004/compat b/distros/ubuntu2004/compat new file mode 100644 index 000000000..48082f72f --- /dev/null +++ b/distros/ubuntu2004/compat @@ -0,0 +1 @@ +12 diff --git a/distros/ubuntu1204/conf/apache2/zoneminder.conf b/distros/ubuntu2004/conf/apache2/zoneminder.conf similarity index 88% rename from distros/ubuntu1204/conf/apache2/zoneminder.conf rename to distros/ubuntu2004/conf/apache2/zoneminder.conf index 8e2957cbf..ae9f0dc3b 100644 --- a/distros/ubuntu1204/conf/apache2/zoneminder.conf +++ b/distros/ubuntu2004/conf/apache2/zoneminder.conf @@ -6,7 +6,8 @@ ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin" Require all granted -# Order matters. This Alias must come first + +# Order matters. This alias must come first. Alias /zm/cache /var/cache/zoneminder/cache Options -Indexes +FollowSymLinks @@ -15,16 +16,10 @@ Alias /zm/cache /var/cache/zoneminder/cache # Apache 2.4 Require all granted - - # Apache 2.2 - Order deny,allow - Allow from all - Alias /zm /usr/share/zoneminder/www - php_flag register_globals off Options -Indexes +FollowSymLinks DirectoryIndex index.php diff --git a/distros/ubuntu1204/control b/distros/ubuntu2004/control similarity index 75% rename from distros/ubuntu1204/control rename to distros/ubuntu2004/control index e5688a421..a73713976 100644 --- a/distros/ubuntu1204/control +++ b/distros/ubuntu2004/control @@ -2,53 +2,52 @@ Source: zoneminder Section: net Priority: optional Maintainer: Isaac Connor -Uploaders: Isaac Connor -Build-Depends: debhelper (>= 9), python-sphinx | python3-sphinx, apache2-dev, dh-linktree +Build-Depends: debhelper (>= 11), sphinx-doc, python3-sphinx, dh-linktree, dh-apache2 ,cmake - ,libx264-dev, libmp4v2-dev - ,libavcodec-dev, libavformat-dev (>= 3:0.svn20090204), libswscale-dev (>= 3:0.svn20090204), libavutil-dev, libavdevice-dev - ,libavresample-dev + ,libavcodec-dev + ,libavformat-dev + ,libavutil-dev + ,libswresample-dev + ,libswscale-dev + ,ffmpeg + ,net-tools ,libbz2-dev - ,libgcrypt-dev ,libcurl4-gnutls-dev - ,libgnutls-openssl-dev - ,libjpeg8-dev|libjpeg9-dev|libjpeg62-turbo-dev, - ,libmysqlclient-dev - ,libpcre3-dev + ,libjpeg-turbo8-dev | libjpeg62-turbo-dev | libjpeg8-dev | libjpeg9-dev + ,libturbojpeg0-dev + ,default-libmysqlclient-dev | libmysqlclient-dev | libmariadbclient-dev-compat ,libpolkit-gobject-1-dev - ,libv4l-dev (>= 0.8.3) [!hurd-any] + ,libv4l-dev [!hurd-any] ,libvlc-dev ,libdate-manip-perl ,libdbd-mysql-perl ,libphp-serialization-perl ,libsys-mmap-perl [!hurd-any] ,libwww-perl - ,libdata-uuid-perl + ,libdata-uuid-perl ,libssl-dev ,libcrypt-eksblowfish-perl ,libdata-entropy-perl -# Unbundled (dh_linktree): - ,libjs-jquery - ,libjs-mootools -Standards-Version: 3.9.4 -Homepage: http://www.zoneminder.com/ -Vcs-Browser: http://anonscm.debian.org/cgit/collab-maint/zoneminder.git -Vcs-Git: git://anonscm.debian.org/collab-maint/zoneminder.git + ,libvncserver-dev + ,libjwt-gnutls-dev|libjwt-dev + ,libgsoap-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 - ,libmp4v2-2, libx264-142 - ,libav-tools|ffmpeg - ,libdate-manip-perl + ,libswscale5|libswscale4 + ,libswresample3|libswresample2 + ,ffmpeg + ,libcurl4, libcurl4-gnutls-dev + ,libdatetime-perl, libdate-manip-perl, libmime-lite-perl, libmime-tools-perl ,libdbd-mysql-perl - ,libmime-lite-perl - ,libmime-tools-perl ,libphp-serialization-perl ,libmodule-load-conditional-perl ,libnet-sftp-foreign-perl -# ,libzoneminder-perl (= ${source:Version}) ,libarchive-zip-perl ,libdbd-mysql-perl ,libdevice-serialport-perl @@ -56,26 +55,30 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libjson-maybexs-perl ,libsys-mmap-perl [!hurd-any] ,liburi-encode-perl - ,libwww-perl + ,libwww-perl, liburi-perl + ,libdata-dump-perl ,libdatetime-perl - ,libdata-uuid-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 | virtual-mysql-client - ,perl-modules - ,php5-mysql, php5-gd, php5-apcu, php-apc + ,default-mysql-client | mariadb-client | virtual-mysql-client + ,php-mysql, php-gd, php-apcu, php-apc | php-apcu-bc, php-json ,policykit-1 ,rsyslog | system-log-daemon ,zip - ,libdata-dump-perl, libclass-std-fast-perl, libsoap-wsdl-perl - ,libio-socket-multicast-perl, libdigest-sha-perl - ,libsys-cpu-perl, libsys-meminfo-perl - ,libssl | libssl1.0.0 ,libcrypt-eksblowfish-perl ,libdata-entropy-perl + ,libvncclient1|libvncclient0 + ,libjwt-gnutls0|libjwt0 + ,libgsoap-2.8.104|libgsoap-2.8.91|libgsoap-2.8.75|libgsoap-2.8.60|libgsoap10 Recommends: ${misc:Recommends} - ,libapache2-mod-php5 | php5-fpm - ,mysql-server | virtual-mysql-server + ,libapache2-mod-php | php-fpm + ,default-mysql-server | mariadb-server | virtual-mysql-server ,zoneminder-doc (>= ${source:Version}) Suggests: fcgiwrap, logrotate Description: video camera security and surveillance solution @@ -122,7 +125,7 @@ Package: zoneminder-doc Section: doc Architecture: all Multi-Arch: foreign -Depends: ${misc:Depends}, ${sphinxdoc:Depends} +Depends: ${misc:Depends}, ${sphinxdoc:Depends}, python-sphinx-rtd-theme | python3-sphinx-rtd-theme Suggests: www-browser Description: ZoneMinder documentation ZoneMinder is intended for use in single or multi-camera video security @@ -140,7 +143,7 @@ Description: ZoneMinder documentation Package: zoneminder-dbg Section: debug -Priority: extra +Priority: optional Architecture: any Depends: zoneminder (= ${binary:Version}), ${misc:Depends} Description: Zoneminder -- debugging symbols diff --git a/distros/ubuntu1604/copyright b/distros/ubuntu2004/copyright similarity index 95% rename from distros/ubuntu1604/copyright rename to distros/ubuntu2004/copyright index c48025a25..eb5274cbf 100644 --- a/distros/ubuntu1604/copyright +++ b/distros/ubuntu2004/copyright @@ -1,6 +1,6 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: ZoneMinder -Upstream-Contact: Philip Coombes +Upstream-Contact: Isaac Connor Source: https://github.com/ZoneMinder/ZoneMinder Comment: This package was originally debianized by matrix @@ -9,7 +9,6 @@ Comment: on Fri, 8 Dec 2006 10:19:43 +1100 Files-Excluded: web/skins/*/js/jquery-* - web/tools/mootools/*-yc.js Files: * Copyright: 2001-2014 Philip Coombes @@ -37,17 +36,11 @@ Comment: Includes Sizzle.js http://sizzlejs.com/ Released under the MIT, BSD, and GPL Licenses. -Files: web/tools/mootools/*.js -Copyright: 2009 Marcelo Jorge Vieira (metal) - 2006-2010 Valerio Proietti (http://mad4milk.net/) -License: Expat - Files: web/api/* Copyright: 2005-2013 Cake Software Foundation, Inc. (http://cakefoundation.org) License: Expat Files: - cmake/Modules/CheckPrototypeDefinition*.cmake cmake/Modules/FindGLIB2.cmake cmake/Modules/FindPolkit.cmake cmake/Modules/GNUInstallDirs.cmake diff --git a/distros/ubuntu1604/examples/nginx.conf b/distros/ubuntu2004/examples/nginx.conf similarity index 100% rename from distros/ubuntu1604/examples/nginx.conf rename to distros/ubuntu2004/examples/nginx.conf diff --git a/distros/ubuntu1604/gbp.conf b/distros/ubuntu2004/gbp.conf similarity index 100% rename from distros/ubuntu1604/gbp.conf rename to distros/ubuntu2004/gbp.conf diff --git a/distros/ubuntu1604/libzoneminder-perl.install b/distros/ubuntu2004/libzoneminder-perl.install similarity index 100% rename from distros/ubuntu1604/libzoneminder-perl.install rename to distros/ubuntu2004/libzoneminder-perl.install diff --git a/distros/ubuntu1410/patches/series b/distros/ubuntu2004/patches/series similarity index 100% rename from distros/ubuntu1410/patches/series rename to distros/ubuntu2004/patches/series diff --git a/distros/ubuntu1204/rules b/distros/ubuntu2004/rules similarity index 52% rename from distros/ubuntu1204/rules rename to distros/ubuntu2004/rules index 657697fcf..af75a409a 100755 --- a/distros/ubuntu1204/rules +++ b/distros/ubuntu2004/rules @@ -1,5 +1,4 @@ #!/usr/bin/make -f -# -*- makefile -*- # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 @@ -12,33 +11,50 @@ ARGS:= -DZM_NO_MMAP=ON endif %: - dh $@ --parallel --buildsystem=cmake --builddirectory=dbuild \ + dh $@ --buildsystem=cmake --builddirectory=dbuild \ --with sphinxdoc,apache2,linktree override_dh_auto_configure: - dh_auto_configure -- $(ARGS) \ - -DCMAKE_VERBOSE_MAKEFILE=ON \ - -DCMAKE_BUILD_TYPE=Release \ - -DZM_CONFIG_DIR="/etc/zm" \ - -DZM_CONFIG_SUBDIR="/etc/zm/conf.d" \ - -DZM_RUNDIR="/var/run/zm" \ - -DZM_SOCKDIR="/var/run/zm" \ - -DZM_TMPDIR="/tmp/zm" \ - -DZM_CGIDIR="/usr/lib/zoneminder/cgi-bin" \ - -DZM_CACHEDIR="/var/cache/zoneminder/cache" \ + dh_auto_configure -- $(ARGS) \ + -DCMAKE_VERBOSE_MAKEFILE=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_MAN=0 \ + -DZM_NO_PCRE=ON \ + -DZM_CONFIG_DIR="/etc/zm" \ + -DZM_CONFIG_SUBDIR="/etc/zm/conf.d" \ + -DZM_RUNDIR="/run/zm" \ + -DZM_SOCKDIR="/run/zm" \ + -DZM_TMPDIR="/var/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" + -DZM_PATH_SHUTDOWN="/sbin/shutdown" \ + -DZM_PATH_ZMS="/zm/cgi-bin/nph-zms" override_dh_clean: dh_clean $(MANPAGES1) - $(RM) -r docs/_build docs/installationguide + $(RM) -r docs/_build -build-indep: - #$(MAKE) -C docs text +override_dh_auto_build-indep: $(MAKE) -C docs html + dh_auto_build + +MANPAGES1 = \ + dbuild/scripts/zmupdate.pl.1 \ + dbuild/scripts/zmaudit.pl.1 \ + dbuild/scripts/zmcamtool.pl.1 \ + dbuild/scripts/zmcontrol.pl.1 \ + dbuild/scripts/zmdc.pl.1 \ + dbuild/scripts/zmfilter.pl.1 \ + dbuild/scripts/zmpkg.pl.1 \ + dbuild/scripts/zmsystemctl.pl.1 \ + dbuild/scripts/zmtelemetry.pl.1 \ + dbuild/scripts/zmtrack.pl.1 \ + dbuild/scripts/zmtrigger.pl.1 \ + dbuild/scripts/zmvideo.pl.1 \ + dbuild/scripts/zmwatch.pl.1 \ + dbuild/scripts/zmx10.pl.1 -MANPAGES1 = dbuild/scripts/zmupdate.pl.1 $(MANPAGES1): # generate man page(s): pod2man -s1 --stderr --utf8 $(patsubst %.1, %, $@) $@ @@ -51,7 +67,7 @@ override_dh_installman: $(MANPAGES1) dh_installman --language=C $(MANPAGES1) override_dh_auto_install: - dh_auto_install --destdir=$(CURDIR)/debian/tmp + dh_auto_install --arch --destdir=$(CURDIR)/debian/tmp # remove worthless files: $(RM) -v $(CURDIR)/debian/tmp/usr/share/perl5/*/*/*/.packlist $(RM) -v $(CURDIR)/debian/tmp/usr/share/perl5/*/*.in @@ -70,6 +86,9 @@ override_dh_fixperms: override_dh_installinit: dh_installinit --no-start +override_dh_installsystemd: + dh_installsystemd --no-enable --no-start + override_dh_apache2: dh_apache2 --noenable @@ -78,16 +97,3 @@ override_dh_strip: && dh_strip --dbg-package=zoneminder-dbg \ || dh_strip -#%: -# dh $@ --parallel --buildsystem=autoconf --with autoreconf -# -#override_dh_auto_configure: -# dh_auto_configure -- \ -# --sysconfdir=/etc/zm \ -# --with-mysql=/usr \ -# --with-webdir=/usr/share/zoneminder \ -# --with-ffmpeg=/usr \ -# --with-cgidir=/usr/lib/cgi-bin \ -# --with-webuser=www-data \ -# --with-webgroup=www-data \ -# --enable-mmap=yes diff --git a/distros/ubuntu1604/source/format b/distros/ubuntu2004/source/format similarity index 100% rename from distros/ubuntu1604/source/format rename to distros/ubuntu2004/source/format diff --git a/distros/ubuntu2004/source/lintian-overrides b/distros/ubuntu2004/source/lintian-overrides new file mode 100644 index 000000000..f905a5a2f --- /dev/null +++ b/distros/ubuntu2004/source/lintian-overrides @@ -0,0 +1,5 @@ +## 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/ubuntu2004/zoneminder-doc.doc-base similarity index 100% rename from distros/ubuntu1604/zoneminder-doc.doc-base rename to distros/ubuntu2004/zoneminder-doc.doc-base diff --git a/distros/ubuntu1604/zoneminder-doc.install b/distros/ubuntu2004/zoneminder-doc.install similarity index 100% rename from distros/ubuntu1604/zoneminder-doc.install rename to distros/ubuntu2004/zoneminder-doc.install diff --git a/distros/ubuntu1604/zoneminder-doc.links b/distros/ubuntu2004/zoneminder-doc.links similarity index 100% rename from distros/ubuntu1604/zoneminder-doc.links rename to distros/ubuntu2004/zoneminder-doc.links diff --git a/distros/ubuntu1604/zoneminder.apache2 b/distros/ubuntu2004/zoneminder.apache2 similarity index 100% rename from distros/ubuntu1604/zoneminder.apache2 rename to distros/ubuntu2004/zoneminder.apache2 diff --git a/distros/ubuntu1604/zoneminder.bug-presubj b/distros/ubuntu2004/zoneminder.bug-presubj similarity index 100% rename from distros/ubuntu1604/zoneminder.bug-presubj rename to distros/ubuntu2004/zoneminder.bug-presubj diff --git a/distros/ubuntu1204/zoneminder.dirs b/distros/ubuntu2004/zoneminder.dirs similarity index 82% rename from distros/ubuntu1204/zoneminder.dirs rename to distros/ubuntu2004/zoneminder.dirs index 79b2c66af..3c7237bf3 100644 --- a/distros/ubuntu1204/zoneminder.dirs +++ b/distros/ubuntu2004/zoneminder.dirs @@ -5,5 +5,6 @@ var/cache/zoneminder/images var/cache/zoneminder/temp var/cache/zoneminder/cache usr/share/zoneminder/db -etc/zm +usr/share/zoneminder/fonts +etc/zm/ etc/zm/conf.d diff --git a/distros/ubuntu1604/zoneminder.docs b/distros/ubuntu2004/zoneminder.docs similarity index 100% rename from distros/ubuntu1604/zoneminder.docs rename to distros/ubuntu2004/zoneminder.docs diff --git a/distros/ubuntu1604/zoneminder.examples b/distros/ubuntu2004/zoneminder.examples similarity index 100% rename from distros/ubuntu1604/zoneminder.examples rename to distros/ubuntu2004/zoneminder.examples diff --git a/distros/ubuntu1410/zoneminder-core.zoneminder.init b/distros/ubuntu2004/zoneminder.init similarity index 78% rename from distros/ubuntu1410/zoneminder-core.zoneminder.init rename to distros/ubuntu2004/zoneminder.init index d3354c1d8..6132481f3 100644 --- a/distros/ubuntu1410/zoneminder-core.zoneminder.init +++ b/distros/ubuntu2004/zoneminder.init @@ -8,23 +8,24 @@ # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Control ZoneMinder as a Service +# Description: ZoneMinder CCTV recording and surveillance system ### END INIT INFO -# description: Control ZoneMinder as a Service # chkconfig: 2345 20 20 # Source function library. -#. /etc/rc.d/init.d/functions +. /lib/lsb/init-functions prog=ZoneMinder ZM_PATH_BIN="/usr/bin" -RUNDIR=/var/run/zm -TMPDIR=/tmp/zm +RUNDIR="/run/zm" +TMPDIR="/tmp/zm" command="$ZM_PATH_BIN/zmpkg.pl" start() { echo -n "Starting $prog: " - mkdir -p $RUNDIR && chown www-data:www-data $RUNDIR - mkdir -p $TMPDIR && chown www-data:www-data $TMPDIR + export TZ=:/etc/localtime + mkdir -p "$RUNDIR" && chown www-data:www-data "$RUNDIR" + mkdir -p "$TMPDIR" && chown www-data:www-data "$TMPDIR" $command start RETVAL=$? [ $RETVAL = 0 ] && echo success @@ -37,11 +38,11 @@ stop() { echo -n "Stopping $prog: " # # Why is this status check being done? - # as $command stop returns 1 if zoneminder - # is stopped, which will result in - # this returning 1, which will stuff + # as $command stop returns 1 if zoneminder + # is stopped, which will result in + # this returning 1, which will stuff # dpkg when it tries to stop zoneminder before - # uninstalling . . . + # uninstalling . . . # result=`$command status` if [ ! "$result" = "running" ]; then diff --git a/distros/ubuntu1604/zoneminder.install b/distros/ubuntu2004/zoneminder.install similarity index 63% rename from distros/ubuntu1604/zoneminder.install rename to distros/ubuntu2004/zoneminder.install index 67b135de5..aa672d29b 100644 --- a/distros/ubuntu1604/zoneminder.install +++ b/distros/ubuntu2004/zoneminder.install @@ -5,7 +5,10 @@ usr/lib/zoneminder usr/share/polkit-1 usr/share/zoneminder/db usr/share/zoneminder/www +usr/share/zoneminder/fonts +usr/share/zoneminder/icons +usr/share/applications/* # libzoneminder-perl files: -usr/share/man/man3 +#usr/share/man/man3 usr/share/perl5 diff --git a/distros/ubuntu2004/zoneminder.links b/distros/ubuntu2004/zoneminder.links new file mode 100644 index 000000000..b7258c3c4 --- /dev/null +++ b/distros/ubuntu2004/zoneminder.links @@ -0,0 +1 @@ +/var/tmp /usr/share/zoneminder/www/api/app/tmp diff --git a/distros/ubuntu1604/zoneminder.lintian-overrides b/distros/ubuntu2004/zoneminder.lintian-overrides similarity index 100% rename from distros/ubuntu1604/zoneminder.lintian-overrides rename to distros/ubuntu2004/zoneminder.lintian-overrides diff --git a/distros/ubuntu1204/zoneminder.logrotate b/distros/ubuntu2004/zoneminder.logrotate similarity index 90% rename from distros/ubuntu1204/zoneminder.logrotate rename to distros/ubuntu2004/zoneminder.logrotate index 3195d0fb2..6162e9c4d 100644 --- a/distros/ubuntu1204/zoneminder.logrotate +++ b/distros/ubuntu2004/zoneminder.logrotate @@ -1,4 +1,4 @@ -/var/log/zm/*log { +/var/log/zm/*.log { missingok notifempty sharedscripts diff --git a/distros/ubuntu1604/zoneminder.maintscript b/distros/ubuntu2004/zoneminder.maintscript similarity index 100% rename from distros/ubuntu1604/zoneminder.maintscript rename to distros/ubuntu2004/zoneminder.maintscript diff --git a/distros/ubuntu1604/zoneminder.manpages b/distros/ubuntu2004/zoneminder.manpages similarity index 100% rename from distros/ubuntu1604/zoneminder.manpages rename to distros/ubuntu2004/zoneminder.manpages diff --git a/distros/ubuntu2004/zoneminder.postinst b/distros/ubuntu2004/zoneminder.postinst new file mode 100644 index 000000000..6f26bca24 --- /dev/null +++ b/distros/ubuntu2004/zoneminder.postinst @@ -0,0 +1,151 @@ +#! /bin/sh + +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" + cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf + if [ $? -ne 0 ]; then + echo "Error creating db." + exit 1; + fi + else + echo "Db exists." + fi +} + +create_update_user () { + USER_EXISTS="$(mysql --defaults-file=/etc/mysql/debian.cnf -sse "SELECT EXISTS(SELECT 1 FROM mysql.user WHERE user = '$ZM_DB_USER')")" + if [ $USER_EXISTS -ne 1 ]; then + echo "Creating zm user $ZM_DB_USER" + # This creates the user. + echo "CREATE USER '${ZM_DB_USER}'@${ZM_DB_HOST} IDENTIFIED BY '${ZM_DB_PASS}';" | mysql --defaults-file=/etc/mysql/debian.cnf mysql + fi + echo "Updating permissions for user ${ZM_DB_USER}@${ZM_DB_HOST}" + echo "GRANT LOCK tables,alter,drop,select,insert,update,delete,create,index,alter routine,create routine,trigger,execute,REFERENCES ON ${ZM_DB_NAME}.* TO '${ZM_DB_USER}'@${ZM_DB_HOST};" | mysql --defaults-file=/etc/mysql/debian.cnf mysql +} + +update_db () { + + zmupdate.pl -s --nointeractive + if [ $? -ne 0 ]; then + echo "Error updating db." + exit 1; + fi + zmupdate.pl --nointeractive -f + if [ $? -ne 0 ]; then + echo "Error updating config." + exit 1; + fi + + # Add any new PTZ control configurations to the database (will not overwrite) + zmcamtool.pl --import >/dev/null 2>&1 + echo "Done Updating" +} + +if [ "$1" = "configure" ]; then + + . /etc/zm/zm.conf + for CONFFILE in /etc/zm/conf.d/*.conf; do + . "$CONFFILE" + done + + # The logs can contain passwords, etc... so by setting group root, only www-data can read them, not people in the www-data group + chown www-data:root /var/log/zm + chown www-data:www-data /var/lib/zm + 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." + if [ -e /usr/share/apache2/apache2-maintscript-helper ] ; then + . /usr/share/apache2/apache2-maintscript-helper + apache2_invoke enmod cgi || exit $? + fi + fi + + SYSTEMD=0 + if [ -e "/run/systemd/system" ]; then + SYSTEMD=1 + echo "detected systemd" + # Ensure zoneminder is stopped + deb-systemd-invoke stop zoneminder.service || exit $? + else + # Ensure zoneminder is stopped + invoke-rc.d zoneminder stop || true + fi + + if [ "$ZM_DB_HOST" = "localhost" ]; then + + if [ $SYSTEMD -eq 1 ] && ([ -e "/lib/systemd/system/mysql.service" ] || [ -e "/lib/systemd/system/mariadb.service" ]); then + # + # Get mysql started if it isn't running + # + + if [ -e "/lib/systemd/system/mariadb.service" ]; then + DBSERVICE="mariadb.service" + else + 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 + create_db + create_update_user + update_db + else + echo 'NOTE: MySQL/MariaDB not running; please start mysql and run dpkg-reconfigure zoneminder when it is running.' + fi + + elif [ -e "/etc/init.d/mysql" ]; then + # + # Get mysql started if it isn't + # + if ! $(/etc/init.d/mysql status >/dev/null 2>&1); then + service mysql start + fi + if $(/etc/init.d/mysql status >/dev/null 2>&1); then + create_db + create_update_user + update_db + else + echo 'NOTE: MySQL/MariaDB not running; please start mysql and run dpkg-reconfigure zoneminder when it is running.' + fi + + else + echo 'MySQL/MariaDB not found; assuming remote server.' + fi + fi + +else + echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)." +fi + +if [ $SYSTEMD -eq 1 ]; then + deb-systemd-invoke restart zoneminder.service +#else + #service zoneminder start || true +fi + +#DEBHELPER# diff --git a/distros/ubuntu1604/zoneminder.postrm b/distros/ubuntu2004/zoneminder.postrm similarity index 100% rename from distros/ubuntu1604/zoneminder.postrm rename to distros/ubuntu2004/zoneminder.postrm diff --git a/distros/ubuntu2004/zoneminder.preinst b/distros/ubuntu2004/zoneminder.preinst new file mode 100644 index 000000000..6088c3ea9 --- /dev/null +++ b/distros/ubuntu2004/zoneminder.preinst @@ -0,0 +1,11 @@ +#!/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/ubuntu2004/zoneminder.service similarity index 83% rename from distros/ubuntu1604/zoneminder.service rename to distros/ubuntu2004/zoneminder.service index cb2d6791e..6645fea04 100644 --- a/distros/ubuntu1604/zoneminder.service +++ b/distros/ubuntu2004/zoneminder.service @@ -5,7 +5,8 @@ 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 +# Override it by placing an override.conf in /etc/systemd/system/zoneminder.service.d +#BindsTo=mysql.service [Service] #User=www-data diff --git a/distros/ubuntu2004/zoneminder.tmpfile b/distros/ubuntu2004/zoneminder.tmpfile new file mode 100644 index 000000000..a92ad2627 --- /dev/null +++ b/distros/ubuntu2004/zoneminder.tmpfile @@ -0,0 +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 7d +d /var/cache/zoneminder/cache 0755 www-data www-data diff --git a/docs/api.rst b/docs/api.rst index 2aaeafb3e..23effc0b7 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -127,7 +127,7 @@ If you are using the old credentials mechanism present in v1.0, then the credent Key lifetime (v2.0) ^^^^^^^^^^^^^^^^^^^^^^ -In version 2.0, it is easy to know when a key will expire before you use it. You can find that out from the ``access_token_expires`` and ``refresh_token_exipres`` values (in seconds) after you decode the JWT key (there are JWT decode libraries for every language you want). You should refresh the keys before the timeout occurs, or you will not be able to use the APIs. +In version 2.0, it is easy to know when a key will expire before you use it. You can find that out from the ``access_token_expires`` and ``refresh_token_expires`` values (in seconds) after you decode the JWT key (there are JWT decode libraries for every language you want). You should refresh the keys before the timeout occurs, or you will not be able to use the APIs. Understanding access/refresh tokens (v2.0) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -482,6 +482,7 @@ Create a Zone &Zone[Units]=Percent\ &Zone[NumCoords]=4\ &Zone[Coords]=0,0 639,0 639,479 0,479\ + &Zone[Area]=307200\ &Zone[AlarmRGB]=16711680\ &Zone[CheckMethod]=Blobs\ &Zone[MinPixelThreshold]=25\ diff --git a/docs/conf.py b/docs/conf.py index 9de7bd820..37ccd9d68 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,9 +15,6 @@ import sys import os -def setup(app): - app.add_stylesheet('zmstyle.css') - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -134,7 +131,9 @@ html_theme = 'default' # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] -#html_style='zmstyles.css' + +html_css_files = [ 'zmstyle.css' ] + # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied diff --git a/docs/faq.rst b/docs/faq.rst index 56b67d082..f23fa2fc1 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -11,23 +11,26 @@ 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. **Purge By Age** -To delete events that are older than 7 days, create a new filter with "Date" set to "less than" and a value of "-7 days", sort by "date/time" in "asc"ending order, then enable the checkbox "delete all matches". You can also use a value of week or week and days: "-2 week" or "-2 week 4 day" +To delete events that are older than 7 days, create a new filter with "End Date" set to "less than" and a value of "-7 days", sort by "date/time" in "asc"ending order, then enable the checkbox "delete all matches". You can also use a value of week or week and days: "-2 week" or "-2 week 4 day" Save with 'Run Filter In Background' enabled to have it run automatically. Optional skip archived events: click on the plus sign next to -7 days to add another condition. "and" "archive status" equal to "unarchived only". @@ -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? --------------------------------------- @@ -534,4 +550,4 @@ The GPL license allows you produce systems based on GPL software provided your s I am having issues with zmNinja and/or Event Notification Server ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -zmNinja and the Event Notification Server are 3rd party solutions. The developer maintains exhaustive `documentation and FAQs `__. Please direct your questions there. \ No newline at end of file +zmNinja and the Event Notification Server are 3rd party solutions. The developer maintains exhaustive `documentation and FAQs `__. Please direct your questions there. diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index 74a9ca81d..a5162d185 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -3,10 +3,206 @@ 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 + +By default MariaDB uses `unix socket authentication`_, so no root user password is required (root MariaDB user access only available to local root Linux user). If you wish, you can set a root MariaDB password (and apply other security tweaks) by running `mariadb-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 +------------------------ + +This procedure will guide you through the installation of ZoneMinder on Debian 10 (Buster). + +**Step 1:** Make sure your system is up to date + +Open a console and use ``su`` command to become root. + +:: + + apt update + apt upgrade + + +**Step 2:** 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 3:** Install Apache and MySQL + +These are not dependencies for the ZoneMinder package as they could be +installed elsewhere. If they are not installed yet in your system, you have to +trigger their installation manually. + +:: + + sudo apt install apache2 default-mysql-server + +**Step 4:** Add ZoneMinder's Package repository to your apt sources + +ZoneMinder's Debian packages are not included in Debian's official package +repositories. To be able to install ZoneMinder with APT, you have to edit the +list of apt sources and add ZoneMinder's repository. + +Add the following to the /etc/apt/sources.list.d/zoneminder.list file + +:: + + # ZoneMinder repository + deb https://zmrepo.zoneminder.com/debian/release-1.36 buster/ + +You can do this using: + +:: + + 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. +:: + + sudo apt install apt-transport-https + +Ensure you have gnupg installed before importing the apt key in the following step. +:: + + sudo apt install gnupg + + +Finally, download the GPG key for ZoneMinder's repository: +:: + + wget -O - https://zmrepo.zoneminder.com/debian/archive-keyring.gpg | sudo apt-key add - + + +**Step 5:** Install ZoneMinder + +:: + + sudo apt update + sudo apt 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:** Enable ZoneMinder service + +:: + + sudo systemctl enable zoneminder.service + +**Step 8:** Configure Apache + +The following commands will setup the default /zm virtual directory and configure +required apache modules. + +:: + + sudo a2enconf zoneminder + sudo a2enmod rewrite # this is enabled by default + sudo a2enmod cgi # this is done automatically when installing the package. Redo this command manually only for troubleshooting. + + +**Step 9:** Edit Timezone in PHP + +Automated way: +:: + + sudo sed -i "s/;date.timezone =/date.timezone = $(sed 's/\//\\\//' /etc/timezone)/g" /etc/php/7.*/apache2/php.ini + +Manual way +:: + + sudo nano /etc/php/7.*/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 10:** Start ZoneMinder + +Reload Apache to enable your changes and then start ZoneMinder. + +:: + + sudo systemctl reload apache2 + 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 Stretch ------------------------ -This procedure will guide you through the installation of ZoneMinder on Debian 9 (Stretch). This section has been tested with ZoneMinder 1.32.3 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 @@ -52,7 +248,7 @@ Add the following to the bottom of the file :: # ZoneMinder repository - deb https://zmrepo.zoneminder.com/debian/release stretch/ + deb https://zmrepo.zoneminder.com/debian/release-1.36 stretch/ CTRL+o and to save CTRL+x to exit @@ -142,175 +338,5 @@ Reload Apache to enable your changes and then 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 - -Zoneminder 1.32.x - 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.29.0", - "apiversion": "1.29.0.1" - } - -**Congratulations** Your installation is complete +.. _unix socket authentication: https://mariadb.com/kb/en/authentication-plugin-unix-socket/ +.. _mariadb-secure-installation: https://mariadb.com/kb/en/mysql_secure_installation/ 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/multiserver.rst b/docs/installationguide/multiserver.rst index 14e52d29f..ce1cd26e6 100644 --- a/docs/installationguide/multiserver.rst +++ b/docs/installationguide/multiserver.rst @@ -21,6 +21,10 @@ New installs 1. Follow the normal instructions for your distro for installing ZoneMinder onto all the ZoneMinder servers in the normal fashion. Only a single database will be needed either as standalone, or on one of the ZoneMinder Servers. +.. sidebar :: Note + + For systemd based linux distros, inspect the zoneminder service file, typically found under /lib/systemd/system. Changes may be required for multiserver to function correctly. For example, the service file may check for a running instance of mysql or mariadb running locally on the server. This check will need to be removed. Rather than edit the service file directly, copy the service file to /etc/systemd/system and edit the file in that location. + 2. On each ZoneMinder server, edit zm.conf. Find the ZM_DB_HOST variable and set it to the name or ip address of your Database Server. Find the ZM_SERVER_HOST and enter a name for this ZoneMinder server. Use a name easily recognizable by you. This name is not used by ZoneMinder for dns or any other form of network conectivity. 3. Copy the file /usr/share/zoneminder/db/zm_create.sql from one of the ZoneMinder Servers to the machine targeted as the Database Server. @@ -46,7 +50,9 @@ Note that these commands are just an example and might not be secure enough for 9. From each ZoneMinder Server, mount the shared events folder on the Storage Server to the events folder on the local ZoneMinder Server. -NOTE: The location of this folder varies by distro. This folder is often found under "/var/lib/zoneminder/events" for RedHat based distros and "/var/cache/zoneminder/events" for Debain based distros. This folder is NOT a Symbolic Link! +.. sidebar :: Note + + The location of the ZoneMinder events folder varies by distro. This folder is often found under "/var/lib/zoneminder/events" for RedHat based distros and "/var/cache/zoneminder/events" for Debain based distros. This folder is NOT a Symbolic Link! 10. Open your browser and point it to the web console on any of the ZoneMinder Servers (they will all be the same). Open Options, click the Servers tab,and populate this screen with all of your ZoneMinder Servers. Each server has a field for its name and its hostname. The name is what you used for ZM_SERVER_HOST in step 2. The hostname is the network name or ip address ZoneMinder should use. diff --git a/docs/installationguide/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/redhat.rst b/docs/installationguide/redhat.rst index d35f1fbd3..b19080865 100644 --- a/docs/installationguide/redhat.rst +++ b/docs/installationguide/redhat.rst @@ -45,15 +45,47 @@ The following notes are based on real problems which have occurred by those who How to Install ZoneMinder ------------------------- -ZoneMinder releases are now being hosted at RPM Fusion. New users should navigate the `RPM Fusion site `__ then follow the instructions to enable that repo. RHEL/CentOS users must also navaigate to the `EPEL Site `_ and enable that repo as well. Once enabled, install ZoneMinder from the commandline: +ZoneMinder releases are hosted at RPM Fusion. New users should navigate to the `RPM Fusion site `__ then follow the instructions to enable that repo. + +.. sidebar :: Note + + RHEL/CentOS 7 users should use *yum* instead of *dnf* + +RHEL/CentOS 7 & 8 users must enable the EPEL repo: :: - sudo dnf install zoneminder + sudo dnf install epel-release + +RHEL/CentOS 8 users must also enable the PowerTools repo: -Note that RHEL/CentOS 7 users should use yum instead of dnf. +:: -Once ZoneMinder has been installed, it is critically important that you read the README file under /usr/share/doc/zoneminder. ZoneMinder will not run without completing the steps outlined in the README. + sudo dnf install dnf-plugins-core + sudo dnf config-manager --set-enabled PowerTools + +Once the additional repos are enabled, install ZoneMinder from the commandline. Choose the package that matches the desired web server. + +Install ZoneMinder for Apache web server: + +.. sidebar :: Note + + A virtual package called zoneminder exists. This package contains no files and will pull in the zoneminder-httpd package for backwards compatiblity. + +:: + + sudo dnf install zoneminder-httpd + +Install ZoneMinder for Nginx web server: + +:: + + sudo dnf install zoneminder-nginx + + +Once ZoneMinder has been installed, you must read the README file to complete the installation. Fedora users can find the README under /usr/share/doc/zoneminder-common. RHEL/CentOS users can find the README under /usr/share/doc/zoneminder-common-x.xx where x.xx is the version of zoneminder. + +ZoneMinder will *NOT* run without completing the steps shown in the README! How to Install Nightly Development Builds ----------------------------------------- @@ -62,21 +94,6 @@ ZoneMinder development packages, which represent the most recent build from our The feedback we get from those who use these development packages is extremely helpful. However, please understand these packages are intended for testing the latest master branch only. They are not intended to be used on any production system. There will be new bugs, and new features may not be documented. This is bleeding edge, and there might be breakage. Please keep that in mind when using this repo. We know from our user forum that this can't be stated enough. -How to Change from Zmrepo to RPM Fusion ---------------------------------------- - -As mentioned above, the place to get the latest ZoneMinder release is now `RPM Fusion `__. If you are currently using ZoneMinder release packages from Zmrepo, then the following steps will change you over to RPM Fusion: - -- Navigate to the `RPM Fusion site `__ and enable RPM Fusion on your system -- Now issue the following from the command line: - -:: - - sudo dnf remove zmrepo - sudo dnf update - -Note that RHEL/CentOS 7 users should use yum instead of dnf. - How to Build Your Own ZoneMinder Package ------------------------------------------ @@ -191,7 +208,7 @@ Now clone the ZoneMinder git repository from your home folder: git clone https://github.com/ZoneMinder/zoneminder cd zoneminder -This will create a sub-folder called ZoneMinder, which will contain the latest development source code. +This will create a sub-folder called zoneminder, which will contain the latest development source code. If you have previsouly cloned the ZoneMinder git repo and wish to update it to the most recent, then issue these commands instead: diff --git a/docs/installationguide/ubuntu.rst b/docs/installationguide/ubuntu.rst index 491ee1eb8..d59d58f38 100644 --- a/docs/installationguide/ubuntu.rst +++ b/docs/installationguide/ubuntu.rst @@ -18,7 +18,6 @@ achieve the same result by running: sudo apt-get install tasksel sudo tasksel install lamp-server -During installation it will ask you to set up a master/root password for the MySQL. Installing LAMP is not ZoneMinder specific so you will find plenty of resources to guide you with a quick search. @@ -42,7 +41,7 @@ guide you with a quick search. :: - add-apt-repository ppa:iconnor/zoneminder-1.32 + add-apt-repository ppa:iconnor/zoneminder-1.36 Update repo and upgrade. @@ -57,7 +56,7 @@ Update repo and upgrade. .. sidebar :: Note - The MySQL default configuration file (/etc/mysql/mysql.cnf)is read through + The MySQL default configuration file (/etc/mysql/mysql.cnf) is read through several symbolic links beginning with /etc/mysql/my.cnf as follows: | /etc/mysql/my.cnf -> /etc/alternatives/my.cnf @@ -66,7 +65,7 @@ Update repo and upgrade. Certain new defaults in MySQL 5.7 cause some issues with ZoneMinder < 1.32.0, the workaround is to modify the sql_mode setting of MySQL. Please note that these -changes are NOT required for ZoneMinder 1.32.0 and some people have reported them +changes are NOT required for ZoneMinder 1.32+ and some people have reported them causing problems in 1.32.0. To better manage the MySQL server it is recommended to copy the sample config file and @@ -113,7 +112,7 @@ This step should not be required on ZoneMinder 1.32.0. :: mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql - mysql -uroot -p -e "grant lock tables,alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute on zm.* to 'zmuser'@localhost identified by 'zmpass';" + mysql -uroot -p -e "grant lock tables,alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute,references on zm.* to 'zmuser'@localhost identified by 'zmpass';" **Step 6:** Set permissions @@ -148,23 +147,6 @@ You may also want to enable to following modules to improve caching performance systemctl enable zoneminder systemctl start zoneminder -**Step 9:** Edit Timezone in PHP - -:: - - nano /etc/php/7.2/apache2/php.ini - -Search for [Date] (Ctrl + w then type Date and press Enter) and change -date.timezone for your time zone, see [this](https://www.php.net/manual/en/timezones.php). -**Don't forget to remove the ; from in front of date.timezone** - -:: - - [Date] - ; Defines the default timezone used by the date functions - ; http://php.net/date.timezone - date.timezone = America/New_York - CTRL+o then [Enter] to save CTRL+x to exit @@ -186,292 +168,14 @@ CTRL+x to exit :: { - "version": "1.29.0", - "apiversion": "1.29.0.1" + "version": "1.34.0", + "apiversion": "1.34.0.1" } **Congratulations** Your installation is complete PPA install may need some tweaking of ZMS_PATH in ZoneMinder options. `Socket_sendto or no live streaming`_ -Easy Way: Ubuntu 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 9:** Edit Timezone in PHP - -:: - - nano /etc/php/7.0/apache2/php.ini - -Search for [Date] (Ctrl + w then type Date and press Enter) and change -date.timezone for your time zone, see [this](https://www.php.net/manual/en/timezones.php). -**Don't forget to remove the ; from in front of date.timezone** - -:: - - [Date] - ; Defines the default timezone used by the date functions - ; http://php.net/date.timezone - date.timezone = America/New_York - -CTRL+o then [Enter] to save - -CTRL+x to exit - -**Step 10:** Reload Apache service - -:: - - 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.29.0", - "apiversion": "1.29.0.1" - } - -**Congratulations** Your installation is complete - -PPA install may need some tweaking of ZMS_PATH in ZoneMinder options. `Socket_sendto or no live streaming`_ - -Easy Way: Ubuntu 14.x (Trusty) ------------------------------- -**These instructions are for a brand new ubuntu 14.x system which does not have ZM installed.** - -**Step 1:** Either run commands in this install using sudo or use the below to become root - -:: - - sudo -i - -**Step 2:** Install ZoneMinder - -:: - - add-apt-repository ppa:iconnor/zoneminder - apt-get update - apt-get install zoneminder - -(just press OK for the prompts you get) - -**Step 3:** Set up DB - -:: - - mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql - mysql -uroot -p -e "grant select,insert,update,delete,create,alter,index,lock tables on zm.* to 'zmuser'@localhost identified by 'zmpass';" - -**Step 4:** Set up Apache - -:: - - a2enconf zoneminder - a2enmod rewrite - a2enmod cgi - -**Step 5:** Make zm.conf readable by web user. - -:: - - sudo chown www-data:www-data /etc/zm/zm.conf - - -**Step 6:** Edit Timezone in PHP - -:: - - nano /etc/php5/apache2/php.ini - -Search for [Date] (Ctrl + w then type Date and press Enter) and change -date.timezone for your time zone, see [this](https://www.php.net/manual/en/timezones.php). -**Don't forget to remove the ; from in front of date.timezone** - -:: - - [Date] - ; Defines the default timezone used by the date functions - ; http://php.net/date.timezone - date.timezone = America/New_York - -CTRL+o then [Enter] to save - -CTRL+x to exit - -**Step 7:** Restart Apache service and start ZoneMinder - -:: - - service apache2 reload - service zoneminder start - - -**Step 8:** Making sure ZoneMinder works - -1. Open up a browser and go to ``http://hostname_or_ip/zm`` - should bring up ZoneMinder Console - -2. (Optional API Check)Open up a tab in the same browser and go to ``http://hostname_or_ip/zm/api/host/getVersion.json`` - - If it is working correctly you should get version information similar to the example below: - - :: - - { - "version": "1.29.0", - "apiversion": "1.29.0.1" - } - -**Congratulations** Your installation is complete Harder Way: Build Package From Source ------------------------------------- @@ -509,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 "vivid", "trusty" 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 @@ -528,7 +232,7 @@ This should now create a bunch of .deb files :: sudo gdebi zoneminder__.deb - (example sudo gdebi zoneminder_1.29.0-vivid-2016012001_amd64.deb) + (example sudo gdebi zoneminder_1.34.0-bionic-2021020801_amd64.deb) **This will report DB errors - ignore - you need to configure the DB and some other stuff** diff --git a/docs/userguide/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 8359aaf89..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. @@ -68,12 +71,23 @@ Here is what the filter window looks like * %ESM% Maximum score of the event * %EP% Path to the event * %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 + * %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 - * %EPIM% Path to the (first) event image with the highest score + * %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 + * %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 @@ -81,7 +95,6 @@ Here is what the filter window looks like * %MEW% Number of events for the monitor in the last week * %MEM% Number of events for the monitor in the last month * %MEA% Number of archived events for the monitor - * %MOD% Path to image containing object detection * %MP% Path to the monitor window * %MPS% Path to the monitor stream * %MPI% Path to the monitor recent image @@ -104,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 509f413a2..61d4cb523 100644 --- a/docs/userguide/gettingstarted.rst +++ b/docs/userguide/gettingstarted.rst @@ -51,9 +51,9 @@ This screen is called the "console" screen in ZoneMinder and shows a summary of * **A**: The options menu lets you configure many aspects of ZoneMinder. Refer to :doc:`options`. * **B**: This brings up a color coded log window that shows various system and component level logs. This window is useful if you are trying to diagnose issues. Refer to :doc:`logging`. -* **C**: ZoneMinder allows you to group monitors gor logical separation. This option lets you create new groups, associate monitors to them and edit/delete existing groups. +* **C**: ZoneMinder allows you to group monitors for logical separation. This option lets you create new groups, associate monitors to them and edit/delete existing groups. * **D**: Filters are a powerful mechanism to perform actions when certain conditions are met. ZoneMinder comes with some preset filters that keep a tab of disk space and others. Many users create their own filters for more advanced actions like sending emails when certain events occur and more. Refer to :doc:`filterevents`. -* **E**: The Cycle option allows you to rotate between live views of each cofigured monitor. +* **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. @@ -109,7 +109,7 @@ This brings up the new monitor window: * In this example, the Function is 'Modect', which means it will start recording if motion is detected on that camera feed. The parameters for what constitutes motion detected is specific in :doc:`definezone` -* In Analytis FPS, we've put in 5FPS here. Note that you should not put an FPS that is greater than the camera FPS. In my case, 5FPS is sufficient for my needs +* In Analysis FPS, we've put in 5FPS here. Note that you should not put an FPS that is greater than the camera FPS. In my case, 5FPS is sufficient for my needs .. note:: Leave Maximum FPS and Alarm Maximum FPS **empty** if you are configuring an IP camera. In older versions of ZoneMinder, you were encouraged to put a value here, but that is no longer recommended. Infact, if you see your feed going much slower than the feed is supposed to go, or you get a lot of buffering/display issues, make sure this is empty. If you need to control camera FPS, please do it directly on the camera (via its own web interface, for example) @@ -123,6 +123,8 @@ This brings up the new monitor window: * Let's select a protocol of RTSP and a remote method of RTP/RTSP (this is an RTSP camera) * Note that starting ZM 1.34, GPUs are supported. In my case, I have an NVIDIA GeForce GTX1050i. These ``cuda`` and ``cuvid`` parameters are what my system supports to use the NVIDIA hardware decoder and GPU resources. If you don't have a GPU, or don't know how to configure your ffmpeg to support it, leave it empty for now. In future, we will add a section on how to set up a GPU +**NOTE**: It is entirely possible that ``cuda`` and ``cuvid`` don't work for you and you need different values. Isaac uses ``cuda`` in ``DecoderHWAccelName`` and leaves ``DecoderHWAccelDevice`` empty. Try that too. + .. todo:: add GPU docs 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_config.rst b/docs/userguide/options/options_config.rst index 9d0e460ac..648987773 100644 --- a/docs/userguide/options/options_config.rst +++ b/docs/userguide/options/options_config.rst @@ -32,12 +32,10 @@ BULK_FRAME_INTERVAL - Traditionally ZoneMinder writes an entry into the Frames d EVENT_CLOSE_MODE - When a monitor is running in a continuous recording mode (Record or Mocord) events are usually closed after a fixed period of time (the section length). However in Mocord mode it is possible that motion detection may occur near the end of a section. This option controls what happens when an alarm occurs in Mocord mode. The 'time' setting means that the event will be closed at the end of the section regardless of alarm activity. The 'idle' setting means that the event will be closed at the end of the section if there is no alarm activity occurring at the time otherwise it will be closed once the alarm is over meaning the event may end up being longer than the normal section length. The 'alarm' setting means that if an alarm occurs during the event, the event will be closed once the alarm is over regardless of when this occurs. This has the effect of limiting the number of alarms to one per event and the events will be shorter than the section length if an alarm has occurred. -CREATE_ANALYSIS_IMAGES - By default during an alarm ZoneMinder records both the raw captured image and one that has been analysed and had areas where motion was detected outlined. This can be very useful during zone configuration or in analysing why events occurred. However it also incurs some overhead and in a stable system may no longer be necessary. This parameter allows you to switch the generation of these images off. - WEIGHTED_ALARM_CENTRES - ZoneMinder will always calculate the centre point of an alarm in a zone to give some indication of where on the screen it is. This can be used by the experimental motion tracking feature or your own custom extensions. In the alarmed or filtered pixels mode this is a simple midpoint between the extents of the detected pxiesl. However in the blob method this can instead be calculated using weighted pixel locations to give more accurate positioning for irregularly shaped blobs. This method, while more precise is also slower and so is turned off by default. EVENT_IMAGE_DIGITS - As event images are captured they are stored to the filesystem with a numerical index. By default this index has three digits so the numbers start 001, 002 etc. This works works for most scenarios as events with more than 999 frames are rarely captured. However if you have extremely long events and use external applications then you may wish to increase this to ensure correct sorting of images in listings etc. Warning, increasing this value on a live system may render existing events unviewable as the event will have been saved with the previous scheme. Decreasing this value should have no ill effects. DEFAULT_ASPECT_RATIO - When specifying the dimensions of monitors you can click a checkbox to ensure that the width stays in the correct ratio to the height, or vice versa. This setting allows you to indicate what the ratio of these settings should be. This should be specified in the format : and the default of 4:3 normally be acceptable but 11:9 is another common setting. If the checkbox is not clicked when specifying monitor dimensions this setting has no effect. -USER_SELF_EDIT - Ordinarily only users with system edit privilege are able to change users details. Switching this option on allows ordinary users to change their passwords and their language settings \ No newline at end of file +USER_SELF_EDIT - Ordinarily only users with system edit privilege are able to change users details. Switching this option on allows ordinary users to change their passwords and their language settings 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_system.rst b/docs/userguide/options/options_system.rst index bd871a1e8..33c59cf1c 100644 --- a/docs/userguide/options/options_system.rst +++ b/docs/userguide/options/options_system.rst @@ -14,13 +14,13 @@ LANG_DEFAULT - ZoneMinder allows the web interface to use languages other than E OPT_USE_AUTH - ZoneMinder can run in two modes. The simplest is an entirely unauthenticated mode where anyone can access ZoneMinder and perform all tasks. This is most suitable for installations where the web server access is limited in other ways. The other mode enables user accounts with varying sets of permissions. Users must login or authenticate to access ZoneMinder and are limited by their defined permissions. Authenticated mode alone should not be relied up for securing Internet connected ZoneMinder. -AUTH_TYPE - ZoneMinder can use two methods to authenticate users when running in authenticated mode. The first is a builtin method where ZoneMinder provides facilities for users to log in and maintains track of their identity. The second method allows interworking with other methods such as http basic authentication which passes an independently authentication 'remote' user via http. In this case ZoneMinder would use the supplied user without additional authentication provided such a user is configured ion ZoneMinder. +AUTH_TYPE - ZoneMinder can use two methods to authenticate users when running in authenticated mode. The first is a builtin method where ZoneMinder provides facilities for users to log in and maintains track of their identity. The second method allows interworking with other methods such as http basic authentication which passes an independently authenticated 'remote' user via http. In this case ZoneMinder would use the supplied user without additional authentication provided such a user is configured in ZoneMinder. AUTH_RELAY - When ZoneMinder is running in authenticated mode it can pass user details between the web pages and the back end processes. There are two methods for doing this. This first is to use a time limited hashed string which contains no direct username or password details, the second method is to pass the username and passwords around in plaintext. This method is not recommend except where you do not have the md5 libraries available on your system or you have a completely isolated system with no external access. You can also switch off authentication relaying if your system is isolated in other ways. -AUTH_HASH_SECRET - When ZoneMinder is running in hashed authenticated mode it is necessary to generate hashed strings containing encrypted sensitive information such as usernames and password. Although these string are reasonably secure the addition of a random secret increases security substantially. Note that if you are using the new token based APIs, then this field is mandatory with ZM 1.34 and above +AUTH_HASH_SECRET - When ZoneMinder is running in hashed authenticated mode it is necessary to generate hashed strings containing encrypted sensitive information such as usernames and passwords. Although these strings are reasonably secure the addition of a random secret increases security substantially. Note that if you are using the new token based APIs, then this field is mandatory with ZM 1.34 and above. -AUTH_HASH_IPS - When ZoneMinder is running in hashed authenticated mode it can optionally include the requesting IP address in the resultant hash. This adds an extra level of security as only requests from that address may use that authentication key. However in some circumstances, such as access over mobile networks, the requesting address can change for each request which will cause most requests to fail. This option allows you to control whether IP addresses are included in the authentication hash on your system. If you experience intermitent problems with authentication, switching this option off may help. It is recommended you keep this off if you use mobile apps like zmNinja over mobile carrier networks - several APNs change the IP very frequently which may result in authentication failure. +AUTH_HASH_IPS - When ZoneMinder is running in hashed authenticated mode it can optionally include the requesting IP address in the resultant hash. This adds an extra level of security as only requests from that address may use that authentication key. However in some circumstances, such as access over mobile networks, the requesting address can change for each request which will cause most requests to fail. This option allows you to control whether IP addresses are included in the authentication hash on your system. If you experience intermitent problems with authentication, switching this option off may help. It is recommended you keep this off if you use mobile apps like zmNinja over mobile carrier networks - several APNs change the IP very frequently which may result in authentication failures. AUTH_HASH_TTL - Time before ZM auth will expire (does not apply to API tokens). The default has traditionally been 2 hours. A new hash will automatically be regenerated at half this value. @@ -34,11 +34,11 @@ OPT_USE_API - A global setting to enable/disable ZoneMinder APIs. If you are usi OPT_USE_LEGACY_AUTH - Starting version 1.34.0, ZoneMinder uses a more secure Authentication mechanism using JWT tokens. Older versions used a less secure MD5 based auth hash. It is recommended you turn this off after you are sure you don't need it. If you are using a 3rd party app that relies on the older API auth mechanisms, you will have to update that app if you turn this off. Note that zmNinja 1.3.057 onwards supports the new token system. -OPT_USE_EVENT_NOTIFICATION - zmeventnotification is a 3rd party event notification server that is used to get notifications for alarms detected by ZoneMinder in real time. zmNinja requires this server for push notifications to mobile phones. This option only enables the server if its already installed. Please visit the `Event Notification Server project site `__ for installation instructions. +OPT_USE_EVENT_NOTIFICATION - zmeventnotification is a 3rd party event notification server that is used to get notifications for alarms detected by ZoneMinder in real time. zmNinja requires this server for push notifications to mobile phones. This option only enables the server if it is already installed. Please visit the `Event Notification Server project site `__ for installation instructions. -OPT_USE_GOOG_RECAPTCHA - This option allows you to include a google reCaptcha validation at login. This means in addition to providing a valid usernane and password, you will also have to pass the reCaptcha test. Please note that enabling this option results in the zoneminder login page reach out to google servers for captcha validation. Also please note that enabling this option may break 3rd party clients if they rely on web based logins (Note that zmNinja now uses the API based token method and will not be affected if reCAPTCHA is enabled). If you enable this, you also need to specify your site and secret key (please refer to context help in the ZoneMinder system screen) +OPT_USE_GOOG_RECAPTCHA - This option allows you to include a google reCaptcha validation at login. This means in addition to providing a valid username and password, you will also have to pass the reCaptcha test. Please note that enabling this option results in the zoneminder login page reaching out to google servers for captcha validation. Also please note that enabling this option may break 3rd party clients if they rely on web based logins (Note that zmNinja now uses the API based token method and will not be affected if reCAPTCHA is enabled). If you enable this, you also need to specify your site and secret key (please refer to context help in the ZoneMinder system screen). -SYSTEM_SHUTDOWN - this option decides if it is allowed to shutdown the full system via the ZM UI. The system will need to have sudo installed and the following added to /etc/sudoers: +SYSTEM_SHUTDOWN - this option puts a poweroff icon in the header of the ZM UI for users with System privilege accessi. This icon will allow the user to shutdown the full system via the ZM UI. The system will need to have sudo installed and the following added to /etc/sudoers: :: @@ -46,9 +46,9 @@ SYSTEM_SHUTDOWN - this option decides if it is allowed to shutdown the full syst to perform the shutdown or reboot -OPT_FAST_DELETE - Normally an event created as the result of an alarm consists of entries in one or more database tables plus the various files associated with it. When deleting events in the browser it can take a long time to remove all of this if your are trying to do a lot of events at once. **NOTE**: It is recommended that you keep this option OFF, unless you are running on an old or low-powered system. +OPT_FAST_DELETE - Normally an event created as the result of an alarm consists of entries in one or more database tables plus the various files associated with it. When deleting events in the browser it can take a long time to remove all of this if youxr are trying to do a lot of events at once. **NOTE**: It is recommended that you keep this option OFF, unless you are running on an old or low-powered system. -FILTER_RELOAD_DELAY - ZoneMinder allows you to save filters to the database which allow events that match certain criteria to be emailed, deleted or uploaded to a remote machine etc. The zmfilter daemon loads these and does the actual operation. This option determines how often in seconds the filters are reloaded from the database to get the latest versions or new filters. If you don't change filters very often this value can be set to a large value. +FILTER_RELOAD_DELAY - ZoneMinder allows you to save filters to the database which allow events that match certain criteria to be emailed, deleted or uploaded to a remote machine etc. The zmfilter daemon loads these and does the actual operation. This option determines how often in seconds the filters are reloaded from the database to get the latest versions or new filters. If you don't change filters very often this value can be set to a large value. As of 1.34.0 filters should be automatically reloaded when saving a filter so this setting should have little effect. FILTER_EXECUTE_INTERVAL - ZoneMinder allows you to save filters to the database which allow events that match certain criteria to be emailed, deleted or uploaded to a remote machine etc. The zmfilter daemon loads these and does the actual operation. This option determines how often the filters are executed on the saved event in the database. If you want a rapid response to new events this should be a smaller value, however this may increase the overall load on the system and affect performance of other elements. @@ -58,9 +58,9 @@ STATUS_UPDATE_INTERVAL - The zmstats daemon performs various db queries related WATCH_CHECK_INTERVAL - The zmwatch daemon checks the image capture performance of the capture daemons to ensure that they have not locked up (rarely a sync error may occur which blocks indefinitely). This option determines how often the daemons are checked. -WATCH_MAX_DELAY - The zmwatch daemon checks the image capture performance of the capture daemons to ensure that they have not locked up (rarely a sync error may occur which blocks indefinitely). This option determines the maximum delay to allow since the last captured frame. The daemon will be restarted if it has not captured any images after this period though the actual restart may take slightly longer in conjunction with the check interval value above. +WATCH_MAX_DELAY - The zmwatch daemon checks the image capture performance of the capture daemons to ensure that they have not locked up (rarely a sync error may occur which blocks indefinitely). This option determines the maximum delay to allow since the last captured frame. The daemon will be restarted if it has not captured any images after this period though the actual restart may take slightly longer in conjunction with the check interval value above. Please note that some cameras can take up to 30 seconds to get a valid image, so this setting should be larger than that. -RUN_AUDIT - The zmaudit daemon exists to check that the saved information in the database and on the filesystem 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 synchronise the two data stores. This option controls whether zmaudit is run in the background and performs these checks and fixes continuously. It is recommended you keep this **OFF** in most systems. +RUN_AUDIT - The zmaudit daemon exists to check that the saved information in the database and on the filesystem 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 synchronise the two data stores. This option controls whether zmaudit is run in the background and performs these checks and fixes continuously. It is recommended you keep this **OFF** in most systems and run it manually if needed after a system crash. AUDIT_CHECK_INTERVAL - The zmaudit daemon exists to check that the saved information in the database and on the filesystem 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 synchronise the two data stores. The default check interval of 900 seconds (15 minutes) is fine for most systems however if you have a very large number of events the process of scanning the database and filesystem may take a long time and impact performance. In this case you may prefer to make this interval much larger to reduce the impact on your system. This option determines how often these checks are performed. @@ -70,11 +70,11 @@ OPT_CONTROL - ZoneMinder includes limited support for controllable cameras. A nu OPT_TRIGGERS - ZoneMinder can interact with external systems which prompt or cancel alarms. This is done via the zmtrigger.pl script. This option indicates whether you want to use these external triggers. Most people will say no here. -CHECK_FOR_UPDATES - From ZoneMinder version 1.17.0 onwards new versions are expected to be more frequent. To save checking manually for each new version ZoneMinder can check with the zoneminder.com website to determine the most recent release. These checks are infrequent, about once per week, and no personal or system information is transmitted other than your current version number. If you do not wish these checks to take place or your ZoneMinder system has no internet access you can switch these check off with this configuration variable +CHECK_FOR_UPDATES - To save checking manually for each new version ZoneMinder can check with the zoneminder.com website to determine the most recent release. These checks are infrequent, about once per week, and no personal or system information is transmitted other than your current version number. If you do not wish these checks to take place or your ZoneMinder system has no internet access you can switch these check off with this configuration variable. TELEMETRY_DATA - Enable collection of usage information of the local system and send it to the ZoneMinder development team. This data will be used to determine things like who and where our customers are, how big their systems are, the underlying hardware and operating system, etc. This is being done for the sole purpose of creating a better product for our target audience. This script is intended to be completely transparent to the end user, and can be disabled from the web console under Options. For more details on what information we collect, please refer to Zoneminder's privacy statement (available in the contextual help of TELEMETRY_DATA on your installation). -UPDATE_CHECK_PROXY - If you use a proxy to access the internet then ZoneMinder needs to know so it can access zoneminder.com to check for updates. If you do use a proxy enter the full proxy url here in the form of ``http://:/`` +UPDATE_CHECK_PROXY - If you use a proxy to access the internet then ZoneMinder needs to know so it can access zoneminder.com to check for updates. If you do use a proxy enter the full proxy url here in the form of ``http://:/``. SHM_KEY - ZoneMinder uses shared memory to speed up communication between modules. To identify the right area to use shared memory keys are used. This option controls what the base key is, each monitor will have it's Id or'ed with this to get the actual key used. You will not normally need to change this value unless it clashes with another instance of ZoneMinder on the same machine. Only the first four hex digits are used, the lower four will be masked out and ignored. 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/CMakeLists.txt b/fonts/CMakeLists.txt new file mode 100644 index 000000000..0d54e8174 --- /dev/null +++ b/fonts/CMakeLists.txt @@ -0,0 +1,4 @@ +# Glob all database upgrade scripts +file(GLOB fontfileslist RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.zmfnt") +# Install the fonts +install(FILES ${fontfileslist} DESTINATION "${ZM_FONTDIR}") diff --git a/fonts/default.zmfnt b/fonts/default.zmfnt new file mode 100644 index 000000000..e13998a39 Binary files /dev/null and b/fonts/default.zmfnt differ diff --git a/misc/CMakeLists.txt b/misc/CMakeLists.txt index f794241a8..c705b63bb 100644 --- a/misc/CMakeLists.txt +++ b/misc/CMakeLists.txt @@ -7,6 +7,8 @@ configure_file(logrotate.conf.in "${CMAKE_CURRENT_BINARY_DIR}/logrotate.conf" @O configure_file(syslog.conf.in "${CMAKE_CURRENT_BINARY_DIR}/syslog.conf" @ONLY) configure_file(com.zoneminder.systemctl.policy.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.policy" @ONLY) configure_file(com.zoneminder.systemctl.rules.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.rules" @ONLY) +configure_file(com.zoneminder.arp-scan.policy.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.policy" @ONLY) +configure_file(com.zoneminder.arp-scan.rules.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.rules" @ONLY) configure_file(zoneminder.service.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.service" @ONLY) configure_file(zoneminder-tmpfiles.conf.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder-tmpfiles.conf" @ONLY) configure_file(zoneminder.desktop.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.desktop" @ONLY) @@ -17,7 +19,10 @@ configure_file(zm-sudo.in "${CMAKE_CURRENT_BINARY_DIR}/zm-sudo" @ONLY) # Install Policykit rules and actions into the proper folders only on systems with systemd if(WITH_SYSTEMD) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.policy" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/actions") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.rules" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/rules.d") -endif(WITH_SYSTEMD) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.policy" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/actions") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.rules" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/rules.d") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.policy" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/actions") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.rules" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/rules.d") +endif() + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.desktop" DESTINATION "${CMAKE_INSTALL_DATADIR}/applications") diff --git a/misc/com.zoneminder.arp-scan.policy.in b/misc/com.zoneminder.arp-scan.policy.in new file mode 100644 index 000000000..7b5b6043e --- /dev/null +++ b/misc/com.zoneminder.arp-scan.policy.in @@ -0,0 +1,21 @@ + + + + + The ZoneMinder Project + http://www.zoneminder.com/ + + + Allow the ZoneMinder webuser to run arp-scan + The ZoneMinder webuser is trusted to run arp-scan + + yes + yes + yes + + /usr/sbin/arp-scan + + + diff --git a/misc/com.zoneminder.arp-scan.rules.in b/misc/com.zoneminder.arp-scan.rules.in new file mode 100644 index 000000000..74ac25af0 --- /dev/null +++ b/misc/com.zoneminder.arp-scan.rules.in @@ -0,0 +1,7 @@ +polkit.addRule(function(action, subject) { + if (action.id == "com.zoneminder.policykit.pkexec.run-arp-scan" && + subject.user != "@WEB_USER@") { + return polkit.Result.NO; + } + +}); 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/misc/janus.jcfg b/misc/janus.jcfg new file mode 100644 index 000000000..a0adffb88 --- /dev/null +++ b/misc/janus.jcfg @@ -0,0 +1,50 @@ +general: { + configs_folder = "/usr/local/etc/janus" # Configuration files folder + plugins_folder = "/usr/local/lib/janus/plugins" # Plugins folder + transports_folder = "/usr/local/lib/janus/transports" # Transports folder + events_folder = "/usr/local/lib/janus/events" # Event handlers folder + loggers_folder = "/usr/local/lib/janus/loggers" # External loggers folder + debug_level = 4 # Debug/logging level, valid values are 0-7 + admin_secret = "janusoverlord" # String that all Janus requests must contain + protected_folders = [ + "/bin", + "/boot", + "/dev", + "/etc", + "/initrd", + "/lib", + "/lib32", + "/lib64", + "/proc", + "/sbin", + "/sys", + "/usr", + "/var", + "/opt/janus/bin", + "/opt/janus/etc", + "/opt/janus/include", + "/opt/janus/lib", + "/opt/janus/lib32", + "/opt/janus/lib64", + "/opt/janus/sbin" + ] +} +media: { + #ipv6 = true + #ipv6_linklocal = true + rtp_port_range = "20000-40000" +} +nat: { + nice_debug = false + ignore_mdns = true + keep_private_host = true + ice_ignore_list = "vmnet" +} + +plugins: { + disable = "libjanus_audiobridge.so,libjanus_echotest.so,libjanus_recordplay.so,libjanus_sip.so,libjanus_textroom.so,libjanus_videocall.so,libjanus_videoroom.so,libjanus_voicemail.so, + libjanus_nosip.so" +} +transports: { + disable = "libjanus_rabbitmq.so, libjanus_pfunix.so,libjanus_websockets.so" +} diff --git a/misc/janus.plugin.streaming.jcfg b/misc/janus.plugin.streaming.jcfg new file mode 100644 index 000000000..f93b15a3e --- /dev/null +++ b/misc/janus.plugin.streaming.jcfg @@ -0,0 +1,4 @@ +general: { + admin_key = "supersecret" + rtp_port_range = "20000-40000" +} diff --git a/misc/janus.transport.http.jcfg b/misc/janus.transport.http.jcfg new file mode 100644 index 000000000..8ae9171ad --- /dev/null +++ b/misc/janus.transport.http.jcfg @@ -0,0 +1,25 @@ +general: { + json = "indented" # Whether the JSON messages should be indented (default), + base_path = "/janus" # Base path to bind to in the web server (plain HTTP only) + http = true # Whether to enable the plain HTTP interface + port = 8088 # Web server HTTP port + https = false # Whether to enable HTTPS (default=false) +} + +# Janus can also expose an admin/monitor endpoint, to allow you to check +# which sessions are up, which handles they're managing, their current +# status and so on. This provides a useful aid when debugging potential +# issues in Janus. The configuration is pretty much the same as the one +# already presented above for the webserver stuff, as the API is very +# similar: choose the base bath for the admin/monitor endpoint (/admin +# by default), ports, etc. Besides, you can specify +# a secret that must be provided in all requests as a crude form of +# authorization mechanism, and partial or full source IPs if you want to +# limit access basing on IP addresses. For security reasons, this +# endpoint is disabled by default, enable it by setting admin_http=true. +admin: { + admin_base_path = "/admin" # Base path to bind to in the admin/monitor web server (plain HTTP only) + admin_http = false # Whether to enable the plain HTTP interface + admin_port = 7088 # Admin/monitor web server HTTP port + admin_https = false # Whether to enable HTTPS (default=false) +} diff --git a/misc/zoneminder.service.in b/misc/zoneminder.service.in index d1cfb36a0..21be2e433 100644 --- a/misc/zoneminder.service.in +++ b/misc/zoneminder.service.in @@ -3,8 +3,8 @@ [Unit] Description=ZoneMinder CCTV recording and security system -After=network.target mysqld.service httpd.service -Requires=mysqld.service httpd.service +After=network.target mysqld.service httpd.service janus.service +Requires=mysqld.service httpd.service janus.service [Service] User=@WEB_USER@ diff --git a/onvif/modules/CMakeLists.txt b/onvif/modules/CMakeLists.txt index d7ddbf466..502970af9 100644 --- a/onvif/modules/CMakeLists.txt +++ b/onvif/modules/CMakeLists.txt @@ -2,14 +2,14 @@ # If this is an out-of-source build, copy the files we need to the binary directory if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Makefile.PL" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/lib" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE) -endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Makefile.PL" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/lib" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE) +endif() # MAKEMAKER_NOECHO_COMMAND previously defined in /scripts/zoneminder/CMakeLists.txt # Add build target for the perl modules -add_custom_target(zmonvifmodules ALL perl Makefile.PL ${ZM_PERL_MM_PARMS} FIRST_MAKEFILE=MakefilePerl DESTDIR="${CMAKE_CURRENT_BINARY_DIR}/output" ${MAKEMAKER_NOECHO_COMMAND} COMMAND make -f MakefilePerl pure_install COMMENT "Building ZoneMinder perl ONVIF proxy module") +add_custom_target(zmonvifmodules ALL perl Makefile.PL ${ZM_PERL_MM_PARMS_FULL} FIRST_MAKEFILE=MakefilePerl DESTDIR="${CMAKE_CURRENT_BINARY_DIR}/output" ${MAKEMAKER_NOECHO_COMMAND} COMMAND make -f MakefilePerl pure_install COMMENT "Building ZoneMinder perl ONVIF proxy module") # Add install target for the perl modules install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/output/" DESTINATION "/") diff --git a/onvif/modules/lib/ONVIF/Client.pm b/onvif/modules/lib/ONVIF/Client.pm index 90bfdd512..738621ded 100644 --- a/onvif/modules/lib/ONVIF/Client.pm +++ b/onvif/modules/lib/ONVIF/Client.pm @@ -44,6 +44,10 @@ require ONVIF::Deserializer::XSD; require ONVIF::Device::Interfaces::Device::DevicePort; require ONVIF::Media::Interfaces::Media::MediaPort; require ONVIF::PTZ::Interfaces::PTZ::PTZPort; +require ONVIF::Analytics::Interfaces::Analytics::AnalyticsEnginePort; +require ONVIF::Analytics::Interfaces::Analytics::RuleEnginePort; + +require WSNotification::Interfaces::WSBaseNotificationSender::NotificationProducerPort; use Data::Dump qw(dump); @@ -70,13 +74,14 @@ my %services_of :ATTR(:default<{}>); my %serializer_of :ATTR(); my %soap_version_of :ATTR(:default<('1.1')>); +my $verbose; # ========================================================================= # private methods sub service { my ($self, $serviceName, $attr) = @_; -#print "service: " . $services_of{${$self}}{$serviceName}{$attr} . "\n"; + #print "service: " . $services_of{${$self}}{$serviceName}{$attr} . "\n"; # Please note that the Std::Class::Fast docs say not to use ident. $services_of{ident $self}{$serviceName}{$attr}; } @@ -114,49 +119,55 @@ sub get_service_urls { my $result = $self->service('device', 'ep')->GetServices( { IncludeCapability => 'true', # boolean - },, + } ); if ( $result ) { - foreach my $svc ( @{ $result->get_Service() } ) { - my $short_name = $namespace_map{$svc->get_Namespace()}; - my $url_svc = $svc->get_XAddr()->get_value(); - if(defined $short_name && defined $url_svc) { -# print "Got $short_name service\n"; - $self->set_service($short_name, 'url', $url_svc); - } - } - # } else { - #print "No results from GetServices: $result\n"; + print "Have results from GetServices\n" if $verbose; + my $services = $result->get_Service(); + if ( $services ) { + foreach my $svc ( @{ $services } ) { + my $short_name = $namespace_map{$svc->get_Namespace()}; + my $url_svc = $svc->get_XAddr()->get_value(); + if ( defined $short_name && defined $url_svc ) { + print "Got $short_name service $url_svc\n" if $verbose; + $self->set_service($short_name, 'url', $url_svc); + } + } # end foreach service + } else { + print "No services from GetServices\n" if $verbose; + } # end if services + } else { + print "No results from GetServices\n" if $verbose; } # Some devices do not support getServices, so we have to try getCapabilities $result = $self->service('device', 'ep')->GetCapabilities( {}, , ); if ( !$result ) { - print "No results from GetCapabilities: $result\n"; + print "No results from GetCapabilities: $result\n" if $verbose; return; } + print "Have results from GetCapabilities: $result\n" if $verbose; # Result is a GetCapabilitiesResponse foreach my $capabilities ( @{ $result->get_Capabilities() } ) { foreach my $capability ( 'PTZ', 'Media', 'Imaging', 'Events', 'Device' ) { if ( my $function = $capabilities->can( "get_$capability" ) ) { my $Services = $function->( $capabilities ); if ( !$Services ) { - print "Nothing returned ffrom get_$capability\n"; + #print "Nothing returned from get_$capability\n"; } else { foreach my $svc ( @{ $Services } ) { # The capability versions don't have a namespace, so just lowercase them. my $short_name = lc $capability; my $url_svc = $svc->get_XAddr()->get_value(); - if( defined $url_svc) { -# print "Got $short_name service\n"; + if ( defined $url_svc ) { + #print "Got $short_name service\n"; $self->set_service($short_name, 'url', $url_svc); } } # end foreach svr } } else { print "No $capability function\n"; - } # end if has a get_ function } # end foreach capability } # end foreach capabilities @@ -185,9 +196,10 @@ sub http_digest { sub BUILD { my ($self, $ident, $args_ref) = @_; - my $url_svc_device = $args_ref->{'url_svc_device'}; - my $soap_version = $args_ref->{'soap_version'}; - if(! $soap_version) { + $verbose = $args_ref->{verbose}; + my $url_svc_device = $args_ref->{url_svc_device}; + my $soap_version = $args_ref->{soap_version}; + if ( !$soap_version ) { $soap_version = '1.1'; } $self->set_soap_version($soap_version); @@ -202,10 +214,10 @@ sub BUILD { # deserializer_args => { strict => 0 } }); - $services_of{$ident}{'device'} = { url => $url_svc_device, ep => $svc_device }; + $services_of{$ident}{device} = { url => $url_svc_device, ep => $svc_device }; # Can't, don't have credentials yet - #$self->get_service_urls(); + # $self->get_service_urls(); } sub get_users { @@ -240,7 +252,7 @@ sub set_credentials { # TODO: snyc device and client time - if ($create_if_not_exists) { + if ( $create_if_not_exists ) { # If GetUsers() is ok but empty then CreateUsers() # if(not get_users()) { # create_user($username, $password); @@ -260,7 +272,7 @@ sub set_credentials { sub create_services { my ($self) = @_; - #$self->get_service_urls(); + $self->get_service_urls(); if ( defined $self->service('media', 'url') ) { $self->set_service('media', 'ep', ONVIF::Media::Interfaces::Media::MediaPort->new({ @@ -276,7 +288,26 @@ sub create_services { # transport => $transport })); } -} + if ( defined $self->service('events', 'url') ) { + $self->set_service('events', 'ep', WSNotification::Interfaces::WSBaseNotificationSender::NotificationProducerPort->new({ + proxy => $self->service('events', 'url'), + serializer => $self->serializer(), +# transport => $transport + })); + } + if ( defined $self->service('analytics', 'url') ) { + $self->set_service('analytics', 'ep', ONVIF::Analytics::Interfaces::Analytics::AnalyticsEnginePort->new({ + proxy => $self->service('analytics', 'url'), + serializer => $self->serializer(), +# transport => $transport + })); + $self->set_service('rules', 'ep', ONVIF::Analytics::Interfaces::Analytics::RuleEnginePort->new({ + proxy => $self->service('analytics', 'url'), + serializer => $self->serializer(), +# transport => $transport + })); + } +} # end sub create_services sub get_endpoint { my ($self, $serviceType) = @_; diff --git a/onvif/modules/lib/ONVIF/Deserializer/XSD.pm b/onvif/modules/lib/ONVIF/Deserializer/XSD.pm index 215a3cb4d..e74129425 100644 --- a/onvif/modules/lib/ONVIF/Deserializer/XSD.pm +++ b/onvif/modules/lib/ONVIF/Deserializer/XSD.pm @@ -33,16 +33,15 @@ use ONVIF::Deserializer::MessageParser; use SOAP::WSDL::Factory::Deserializer; -SOAP::WSDL::Factory::Deserializer->register('1.1', __PACKAGE__ ); -SOAP::WSDL::Factory::Deserializer->register('1.2', __PACKAGE__ ); +SOAP::WSDL::Factory::Deserializer->register('1.1', __PACKAGE__); +SOAP::WSDL::Factory::Deserializer->register('1.2', __PACKAGE__); ## we get the soap version from the message parser my %soap_version_of :ATTR( :default<()>); - sub soap_version { my ($self) = @_; - if($SOAP::WSDL::Deserializer::XSD::parser_of{ident $self}) { + if ( $SOAP::WSDL::Deserializer::XSD::parser_of{ident $self} ) { return $SOAP::WSDL::Deserializer::XSD::parser_of{ident $self}->soap_version(); } return ''; @@ -52,7 +51,7 @@ sub deserialize { my ($self, $content) = @_; my $parser = $SOAP::WSDL::Deserializer::XSD::parser_of{ ${ $self } }; - if(not $parser) { + if ( not $parser ) { $parser = ONVIF::Deserializer::MessageParser->new({ strict => $SOAP::WSDL::Deserializer::XSD::strict_of{ ${ $self } } }); @@ -61,8 +60,8 @@ sub deserialize { $parser->class_resolver( $self->SOAP::WSDL::Deserializer::XSD::get_class_resolver() ); - eval { $parser->parse_string( $content ) }; - if ($@) { + eval { $parser->parse_string($content) }; + if ( $@ ) { return $self->generate_fault({ code => 'SOAP-ENV:Server', role => 'urn:localhost', @@ -79,12 +78,9 @@ sub generate_fault { return SOAP::WSDL::SOAP::Typelib::Fault11->new({ faultcode => $args_from_ref->{ code } || 'SOAP-ENV:Client', faultactor => $args_from_ref->{ role } || 'urn:localhost', - faultstring => $args_from_ref->{ message } || "Unknown error" + faultstring => $args_from_ref->{ message } || 'Unknown error' }); } 1; - __END__ - - diff --git a/onvif/modules/lib/WSDiscovery/TransportUDP.pm b/onvif/modules/lib/WSDiscovery/TransportUDP.pm index 33308db13..5125ac9f0 100644 --- a/onvif/modules/lib/WSDiscovery/TransportUDP.pm +++ b/onvif/modules/lib/WSDiscovery/TransportUDP.pm @@ -43,7 +43,7 @@ my %message_of :ATTR(:name :default<()>); my %is_success_of :ATTR(:name :default<()>); my %local_addr_of :ATTR(:name :init_arg :default<()>); - +my $net_interface; # create methods normally inherited from SOAP::Client SUBFACTORY: { @@ -60,14 +60,22 @@ sub _notify_response } +sub set_net_interface { + my $self = shift; + $net_interface = shift; +} + sub send_multi() { my ($self, $address, $port, $utf8_string) = @_; my $destination = $address . ':' . $port; - my $socket = IO::Socket::Multicast->new(PROTO => 'udp', - LocalPort=>$port, PeerAddr=>$destination, ReuseAddr=>1) - - or die 'Cannot open multicast socket to ' . ${address} . ':' . ${port}; + my $socket = IO::Socket::Multicast->new( + PROTO => 'udp', + LocalPort=>$port, + PeerAddr=>$destination, + ReuseAddr=>1 + ) or die 'Cannot open multicast socket to ' . ${address} . ':' . ${port}; + $_ = $socket->mcast_if($net_interface) if $net_interface; my $bytes = $utf8_string; utf8::encode($bytes); @@ -80,14 +88,16 @@ sub receive_multi() { my ($self, $address, $port) = @_; my $data = undef; - my $socket = IO::Socket::Multicast->new(PROTO => 'udp', - LocalPort=>$port, ReuseAddr=>1); - $socket->mcast_add($address); + my $socket = IO::Socket::Multicast->new( + PROTO => 'udp', + LocalPort=>$port, + ReuseAddr=>1); + $socket->mcast_add($address, $net_interface); my $readbits = ''; vec($readbits, $socket->fileno, 1) = 1; - if(select($readbits, undef, undef, WAIT_TIME/1000)) { + if ( select($readbits, undef, undef, WAIT_TIME/1000) ) { $socket->recv($data, 9999); return $data; } @@ -98,15 +108,19 @@ sub receive_uni() { my ($self, $address, $port, $localaddr) = @_; my $data = undef; - my $socket = IO::Socket::Multicast->new(PROTO => 'udp', - LocalAddr => $localaddr, LocalPort=>$port, ReuseAddr=>1); + my $socket = IO::Socket::Multicast->new( + PROTO => 'udp', + LocalAddr => $localaddr, + LocalPort=>$port, + ReuseAddr=>1 + ); - $socket->mcast_add($address); + $socket->mcast_add($address, $net_interface); my $readbits = ''; vec($readbits, $socket->fileno, 1) = 1; - if(select($readbits, undef, undef, WAIT_TIME/1000)) { + if ( select($readbits, undef, undef, WAIT_TIME/1000) ) { $socket->recv($data, 9999); return $data; } @@ -114,50 +128,51 @@ sub receive_uni() { } sub send_receive { - my ($self, %parameters) = @_; - my ($envelope, $soap_action, $endpoint, $encoding, $content_type) = - @parameters{qw(envelope action endpoint encoding content_type)}; + my ($self, %parameters) = @_; + my ($envelope, $soap_action, $endpoint, $encoding, $content_type) = + @parameters{qw(envelope action endpoint encoding content_type)}; - my ($address,$port) = ($endpoint =~ /([^:\/]+):([0-9]+)/); + my ($address,$port) = ($endpoint =~ /([^:\/]+):([0-9]+)/); - #warn "address = ${address}"; - #warn "port = ${port}"; +#warn "address = ${address}"; +#warn "port = ${port}"; - $self->send_multi($address, $port, $envelope); + $self->send_multi($address, $port, $envelope); - my $localaddr = $self->get_local_addr(); + my $localaddr = $self->get_local_addr(); +#warn "localddr $localaddr"; - my ($response, $last_response); - my $wait = WAIT_COUNT; - while ( $wait >= 0 ) { - if($localaddr) { - if($response = $self->receive_uni($address, $port, $localaddr)) { - $last_response = $response; - $self->_notify_response($response); - } - $wait --; - } - if($response = $self->receive_multi($address, $port)) { - $last_response = $response; - $self->_notify_response($response); - } - $wait --; - } - - if($last_response) { - $self->set_code(); - $self->set_message(""); - $self->set_is_success(1); - $self->set_status('OK'); - } - else{ - $self->set_code(); - $self->set_message("Timed out waiting for response"); - $self->set_is_success(0); - $self->set_status('TIMEOUT'); - } + my ($response, $last_response); + my $wait = WAIT_COUNT; + while ( $wait >= 0 ) { + if ( $localaddr ) { + if ( $response = $self->receive_uni($address, $port, $localaddr) ) { + $last_response = $response; + $self->_notify_response($response); + } + $wait --; + } + if ( $response = $self->receive_multi($address, $port) ) { + $last_response = $response; + $self->_notify_response($response); + } + $wait --; + } - return $last_response; + if ( $last_response ) { + $self->set_code(); + $self->set_message(''); + $self->set_is_success(1); + $self->set_status('OK'); + } else { + $self->set_code(); + $self->set_message('Timed out waiting for response'); + $self->set_is_success(0); + $self->set_status('TIMEOUT'); + } + + return $last_response; } 1; +__END__ diff --git a/onvif/proxy/CMakeLists.txt b/onvif/proxy/CMakeLists.txt index 1a40c0ffb..779b4d47e 100644 --- a/onvif/proxy/CMakeLists.txt +++ b/onvif/proxy/CMakeLists.txt @@ -2,14 +2,14 @@ # If this is an out-of-source build, copy the files we need to the binary directory if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Makefile.PL" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/lib" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE) -endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Makefile.PL" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/lib" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE) +endif() # MAKEMAKER_NOECHO_COMMAND previously defined in /scripts/zoneminder/CMakeLists.txt # Add build target for the perl modules -add_custom_target(zmonvifproxy ALL perl Makefile.PL ${ZM_PERL_MM_PARMS} FIRST_MAKEFILE=MakefilePerl DESTDIR="${CMAKE_CURRENT_BINARY_DIR}/output" ${MAKEMAKER_NOECHO_COMMAND} COMMAND make -f MakefilePerl pure_install COMMENT "Building ZoneMinder perl ONVIF proxy module") +add_custom_target(zmonvifproxy ALL perl Makefile.PL ${ZM_PERL_MM_PARMS_FULL} FIRST_MAKEFILE=MakefilePerl DESTDIR=${CMAKE_CURRENT_BINARY_DIR}/output ${MAKEMAKER_NOECHO_COMMAND} COMMAND make -f MakefilePerl pure_install COMMENT "Building ZoneMinder perl ONVIF proxy module") # Add install target for the perl modules install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/output/" DESTINATION "/") diff --git a/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 Lcan('get_class'); +if not ONVIF::Media::Typemaps::Media->can('get_class'); sub START { - $_[0]->set_proxy('http://www.examples.com/Media/') if not $_[2]->{proxy}; - $_[0]->set_class_resolver('ONVIF::Media::Typemaps::Media') - if not $_[2]->{class_resolver}; + $_[0]->set_proxy('http://www.examples.com/Media/') if not $_[2]->{proxy}; + $_[0]->set_class_resolver('ONVIF::Media::Typemaps::Media') + if not $_[2]->{class_resolver}; - $_[0]->set_prefix($_[2]->{use_prefix}) if exists $_[2]->{use_prefix}; + $_[0]->set_prefix($_[2]->{use_prefix}) if exists $_[2]->{use_prefix}; } sub GetServiceCapabilities { - my ($self, $body, $header) = @_; - die "GetServiceCapabilities must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetServiceCapabilities', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetServiceCapabilities', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetServiceCapabilities must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetServiceCapabilities', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetServiceCapabilities', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetServiceCapabilities )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetServiceCapabilities )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetVideoSources { - my ($self, $body, $header) = @_; - die "GetVideoSources must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetVideoSources', - soap_action => 'http://www.onvif.org/ver10/media/wsdlGetVideoSources/', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetVideoSources must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetVideoSources', + soap_action => 'http://www.onvif.org/ver10/media/wsdlGetVideoSources/', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetVideoSources )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetVideoSources )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetAudioSources { - my ($self, $body, $header) = @_; - die "GetAudioSources must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetAudioSources', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioSources', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetAudioSources must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetAudioSources', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioSources', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetAudioSources )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetAudioSources )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } - sub GetAudioOutputs { - my ($self, $body, $header) = @_; - die "GetAudioOutputs must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetAudioOutputs', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioOutputs', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetAudioOutputs must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetAudioOutputs', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioOutputs', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetAudioOutputs )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetAudioOutputs )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } - sub CreateProfile { - my ($self, $body, $header) = @_; - die "CreateProfile must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'CreateProfile', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/CreateProfile', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "CreateProfile must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'CreateProfile', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/CreateProfile', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::CreateProfile )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::CreateProfile )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetProfile { - my ($self, $body, $header) = @_; - die "GetProfile must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetProfile', - soap_action => 'http://www.onvif.org/ver10/media/wsdlGetProfile/', - style => 'document', - body => { - - - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetProfile )], - }, - header => { - - }, - headerfault => { - - } + my ($self, $body, $header) = @_; + die "GetProfile must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetProfile', + soap_action => 'http://www.onvif.org/ver10/media/wsdlGetProfile/', + style => 'document', + body => { + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetProfile )], + }, + header => { + }, + headerfault => { + } }, $body, $header); } - sub GetProfiles { - my ($self, $body, $header) = @_; - die "GetProfiles must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetProfiles', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetProfiles', - style => 'document', - body => { - - - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetProfiles )], - }, - header => { - - }, - headerfault => { - - } + my ($self, $body, $header) = @_; + die "GetProfiles must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetProfiles', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetProfiles', + style => 'document', + body => { + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetProfiles )], + }, + header => { + }, + headerfault => { + } }, $body, $header); } sub AddVideoEncoderConfiguration { - my ($self, $body, $header) = @_; - die "AddVideoEncoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'AddVideoEncoderConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddVideoEncoderConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "AddVideoEncoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'AddVideoEncoderConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddVideoEncoderConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::AddVideoEncoderConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::AddVideoEncoderConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub AddVideoSourceConfiguration { - my ($self, $body, $header) = @_; - die "AddVideoSourceConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'AddVideoSourceConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddVideoSourceConfiguration', - style => 'document', - body => { - - - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::AddVideoSourceConfiguration )], - }, - header => { - - }, - headerfault => { - - } + my ($self, $body, $header) = @_; + die "AddVideoSourceConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'AddVideoSourceConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddVideoSourceConfiguration', + style => 'document', + body => { + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::AddVideoSourceConfiguration )], + }, + header => { + }, + headerfault => { + } }, $body, $header); } - sub AddAudioEncoderConfiguration { - my ($self, $body, $header) = @_; - die "AddAudioEncoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'AddAudioEncoderConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddAudioEncoderConfiguration', - style => 'document', - body => { - - - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::AddAudioEncoderConfiguration )], - }, - header => { - - }, - headerfault => { - - } + my ($self, $body, $header) = @_; + die "AddAudioEncoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'AddAudioEncoderConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddAudioEncoderConfiguration', + style => 'document', + body => { + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::AddAudioEncoderConfiguration )], + }, + header => { + }, + headerfault => { + } }, $body, $header); } sub AddAudioSourceConfiguration { - my ($self, $body, $header) = @_; - die "AddAudioSourceConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'AddAudioSourceConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddAudioSourceConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "AddAudioSourceConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'AddAudioSourceConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddAudioSourceConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::AddAudioSourceConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::AddAudioSourceConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub AddPTZConfiguration { - my ($self, $body, $header) = @_; - die "AddPTZConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'AddPTZConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddPTZConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "AddPTZConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'AddPTZConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddPTZConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::AddPTZConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::AddPTZConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub AddVideoAnalyticsConfiguration { - my ($self, $body, $header) = @_; - die "AddVideoAnalyticsConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'AddVideoAnalyticsConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddVideoAnalyticsConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "AddVideoAnalyticsConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'AddVideoAnalyticsConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddVideoAnalyticsConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::AddVideoAnalyticsConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::AddVideoAnalyticsConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub AddMetadataConfiguration { - my ($self, $body, $header) = @_; - die "AddMetadataConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'AddMetadataConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddMetadataConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "AddMetadataConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'AddMetadataConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddMetadataConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::AddMetadataConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::AddMetadataConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub AddAudioOutputConfiguration { - my ($self, $body, $header) = @_; - die "AddAudioOutputConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'AddAudioOutputConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddAudioOutputConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "AddAudioOutputConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'AddAudioOutputConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddAudioOutputConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::AddAudioOutputConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::AddAudioOutputConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub AddAudioDecoderConfiguration { - my ($self, $body, $header) = @_; - die "AddAudioDecoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'AddAudioDecoderConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddAudioDecoderConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "AddAudioDecoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'AddAudioDecoderConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/AddAudioDecoderConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::AddAudioDecoderConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::AddAudioDecoderConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub RemoveVideoEncoderConfiguration { - my ($self, $body, $header) = @_; - die "RemoveVideoEncoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'RemoveVideoEncoderConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemoveVideoEncoderConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "RemoveVideoEncoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'RemoveVideoEncoderConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemoveVideoEncoderConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::RemoveVideoEncoderConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::RemoveVideoEncoderConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub RemoveVideoSourceConfiguration { - my ($self, $body, $header) = @_; - die "RemoveVideoSourceConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'RemoveVideoSourceConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemoveVideoSourceConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "RemoveVideoSourceConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'RemoveVideoSourceConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemoveVideoSourceConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::RemoveVideoSourceConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::RemoveVideoSourceConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub RemoveAudioEncoderConfiguration { - my ($self, $body, $header) = @_; - die "RemoveAudioEncoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'RemoveAudioEncoderConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemoveAudioEncoderConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "RemoveAudioEncoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'RemoveAudioEncoderConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemoveAudioEncoderConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::RemoveAudioEncoderConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::RemoveAudioEncoderConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub RemoveAudioSourceConfiguration { - my ($self, $body, $header) = @_; - die "RemoveAudioSourceConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'RemoveAudioSourceConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemoveAudioSourceConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "RemoveAudioSourceConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'RemoveAudioSourceConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemoveAudioSourceConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::RemoveAudioSourceConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::RemoveAudioSourceConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub RemovePTZConfiguration { - my ($self, $body, $header) = @_; - die "RemovePTZConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'RemovePTZConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemovePTZConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "RemovePTZConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'RemovePTZConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemovePTZConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::RemovePTZConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::RemovePTZConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub RemoveVideoAnalyticsConfiguration { - my ($self, $body, $header) = @_; - die "RemoveVideoAnalyticsConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'RemoveVideoAnalyticsConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemoveVideoAnalyticsConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "RemoveVideoAnalyticsConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'RemoveVideoAnalyticsConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemoveVideoAnalyticsConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::RemoveVideoAnalyticsConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::RemoveVideoAnalyticsConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub RemoveMetadataConfiguration { - my ($self, $body, $header) = @_; - die "RemoveMetadataConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'RemoveMetadataConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemoveMetadataConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "RemoveMetadataConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'RemoveMetadataConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemoveMetadataConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::RemoveMetadataConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::RemoveMetadataConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub RemoveAudioOutputConfiguration { - my ($self, $body, $header) = @_; - die "RemoveAudioOutputConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'RemoveAudioOutputConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemoveAudioOutputConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "RemoveAudioOutputConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'RemoveAudioOutputConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemoveAudioOutputConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::RemoveAudioOutputConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::RemoveAudioOutputConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub RemoveAudioDecoderConfiguration { - my ($self, $body, $header) = @_; - die "RemoveAudioDecoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'RemoveAudioDecoderConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemoveAudioDecoderConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "RemoveAudioDecoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'RemoveAudioDecoderConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/RemoveAudioDecoderConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::RemoveAudioDecoderConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::RemoveAudioDecoderConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub DeleteProfile { - my ($self, $body, $header) = @_; - die "DeleteProfile must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'DeleteProfile', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/DeleteProfile', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "DeleteProfile must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'DeleteProfile', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/DeleteProfile', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::DeleteProfile )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::DeleteProfile )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetVideoSourceConfigurations { - my ($self, $body, $header) = @_; - die "GetVideoSourceConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetVideoSourceConfigurations', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetVideoSourceConfigurations', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetVideoSourceConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetVideoSourceConfigurations', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetVideoSourceConfigurations', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetVideoSourceConfigurations )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetVideoSourceConfigurations )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetVideoEncoderConfigurations { - my ($self, $body, $header) = @_; - die "GetVideoEncoderConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetVideoEncoderConfigurations', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetVideoEncoderConfigurations', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetVideoEncoderConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetVideoEncoderConfigurations', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetVideoEncoderConfigurations', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetVideoEncoderConfigurations )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetVideoEncoderConfigurations )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetAudioSourceConfigurations { - my ($self, $body, $header) = @_; - die "GetAudioSourceConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetAudioSourceConfigurations', - soap_action => 'http://www.onvif.org/ver10/media/wsdlGetAudioSourceConfigurations/', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetAudioSourceConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetAudioSourceConfigurations', + soap_action => 'http://www.onvif.org/ver10/media/wsdlGetAudioSourceConfigurations/', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetAudioSourceConfigurations )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetAudioSourceConfigurations )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetAudioEncoderConfigurations { - my ($self, $body, $header) = @_; - die "GetAudioEncoderConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetAudioEncoderConfigurations', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioEncoderConfigurations', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetAudioEncoderConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetAudioEncoderConfigurations', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioEncoderConfigurations', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetAudioEncoderConfigurations )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetAudioEncoderConfigurations )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetVideoAnalyticsConfigurations { - my ($self, $body, $header) = @_; - die "GetVideoAnalyticsConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetVideoAnalyticsConfigurations', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetVideoAnalyticsConfigurations', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetVideoAnalyticsConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetVideoAnalyticsConfigurations', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetVideoAnalyticsConfigurations', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetVideoAnalyticsConfigurations )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetVideoAnalyticsConfigurations )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetMetadataConfigurations { - my ($self, $body, $header) = @_; - die "GetMetadataConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetMetadataConfigurations', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetMetadataConfigurations', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetMetadataConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetMetadataConfigurations', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetMetadataConfigurations', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetMetadataConfigurations )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetMetadataConfigurations )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetAudioOutputConfigurations { - my ($self, $body, $header) = @_; - die "GetAudioOutputConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetAudioOutputConfigurations', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioOutputConfigurations', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetAudioOutputConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetAudioOutputConfigurations', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioOutputConfigurations', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetAudioOutputConfigurations )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetAudioOutputConfigurations )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetAudioDecoderConfigurations { - my ($self, $body, $header) = @_; - die "GetAudioDecoderConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetAudioDecoderConfigurations', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioDecoderConfigurations', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetAudioDecoderConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetAudioDecoderConfigurations', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioDecoderConfigurations', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetAudioDecoderConfigurations )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetAudioDecoderConfigurations )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetVideoSourceConfiguration { - my ($self, $body, $header) = @_; - die "GetVideoSourceConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetVideoSourceConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetVideoSourceConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetVideoSourceConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetVideoSourceConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetVideoSourceConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetVideoSourceConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetVideoSourceConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetVideoEncoderConfiguration { - my ($self, $body, $header) = @_; - die "GetVideoEncoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetVideoEncoderConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetVideoEncoderConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetVideoEncoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetVideoEncoderConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetVideoEncoderConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetVideoEncoderConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetVideoEncoderConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetAudioSourceConfiguration { - my ($self, $body, $header) = @_; - die "GetAudioSourceConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetAudioSourceConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioSourceConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetAudioSourceConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetAudioSourceConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioSourceConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetAudioSourceConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetAudioSourceConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetAudioEncoderConfiguration { - my ($self, $body, $header) = @_; - die "GetAudioEncoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetAudioEncoderConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioEncoderConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetAudioEncoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetAudioEncoderConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioEncoderConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetAudioEncoderConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetAudioEncoderConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetVideoAnalyticsConfiguration { - my ($self, $body, $header) = @_; - die "GetVideoAnalyticsConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetVideoAnalyticsConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetVideoAnalyticsConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetVideoAnalyticsConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetVideoAnalyticsConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetVideoAnalyticsConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetVideoAnalyticsConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetVideoAnalyticsConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetMetadataConfiguration { - my ($self, $body, $header) = @_; - die "GetMetadataConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetMetadataConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetMetadataConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetMetadataConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetMetadataConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetMetadataConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetMetadataConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetMetadataConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetAudioOutputConfiguration { - my ($self, $body, $header) = @_; - die "GetAudioOutputConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetAudioOutputConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioOutputConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetAudioOutputConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetAudioOutputConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioOutputConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetAudioOutputConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetAudioOutputConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetAudioDecoderConfiguration { - my ($self, $body, $header) = @_; - die "GetAudioDecoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetAudioDecoderConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioDecoderConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetAudioDecoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetAudioDecoderConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioDecoderConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetAudioDecoderConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetAudioDecoderConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetCompatibleVideoEncoderConfigurations { - my ($self, $body, $header) = @_; - die "GetCompatibleVideoEncoderConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetCompatibleVideoEncoderConfigurations', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetCompatibleVideoEncoderConfigurations', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetCompatibleVideoEncoderConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetCompatibleVideoEncoderConfigurations', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetCompatibleVideoEncoderConfigurations', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetCompatibleVideoEncoderConfigurations )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetCompatibleVideoEncoderConfigurations )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetCompatibleVideoSourceConfigurations { - my ($self, $body, $header) = @_; - die "GetCompatibleVideoSourceConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetCompatibleVideoSourceConfigurations', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetCompatibleVideoSourceConfigurations', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetCompatibleVideoSourceConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetCompatibleVideoSourceConfigurations', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetCompatibleVideoSourceConfigurations', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetCompatibleVideoSourceConfigurations )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetCompatibleVideoSourceConfigurations )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetCompatibleAudioEncoderConfigurations { - my ($self, $body, $header) = @_; - die "GetCompatibleAudioEncoderConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetCompatibleAudioEncoderConfigurations', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetCompatibleAudioEncoderConfigurations', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetCompatibleAudioEncoderConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetCompatibleAudioEncoderConfigurations', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetCompatibleAudioEncoderConfigurations', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetCompatibleAudioEncoderConfigurations )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetCompatibleAudioEncoderConfigurations )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetCompatibleAudioSourceConfigurations { - my ($self, $body, $header) = @_; - die "GetCompatibleAudioSourceConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetCompatibleAudioSourceConfigurations', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetCompatibleAudioSourceConfigurations', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetCompatibleAudioSourceConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetCompatibleAudioSourceConfigurations', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetCompatibleAudioSourceConfigurations', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetCompatibleAudioSourceConfigurations )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetCompatibleAudioSourceConfigurations )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetCompatibleVideoAnalyticsConfigurations { - my ($self, $body, $header) = @_; - die "GetCompatibleVideoAnalyticsConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetCompatibleVideoAnalyticsConfigurations', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetCompatibleVideoAnalyticsConfigurations', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetCompatibleVideoAnalyticsConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetCompatibleVideoAnalyticsConfigurations', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetCompatibleVideoAnalyticsConfigurations', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetCompatibleVideoAnalyticsConfigurations )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetCompatibleVideoAnalyticsConfigurations )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetCompatibleMetadataConfigurations { - my ($self, $body, $header) = @_; - die "GetCompatibleMetadataConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetCompatibleMetadataConfigurations', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetCompatibleMetadataConfigurations', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetCompatibleMetadataConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetCompatibleMetadataConfigurations', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetCompatibleMetadataConfigurations', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetCompatibleMetadataConfigurations )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetCompatibleMetadataConfigurations )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetCompatibleAudioOutputConfigurations { - my ($self, $body, $header) = @_; - die "GetCompatibleAudioOutputConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetCompatibleAudioOutputConfigurations', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetCompatibleAudioOutputConfigurations', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetCompatibleAudioOutputConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetCompatibleAudioOutputConfigurations', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetCompatibleAudioOutputConfigurations', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetCompatibleAudioOutputConfigurations )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetCompatibleAudioOutputConfigurations )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetCompatibleAudioDecoderConfigurations { - my ($self, $body, $header) = @_; - die "GetCompatibleAudioDecoderConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetCompatibleAudioDecoderConfigurations', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetCompatibleAudioDecoderConfigurations', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetCompatibleAudioDecoderConfigurations must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetCompatibleAudioDecoderConfigurations', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetCompatibleAudioDecoderConfigurations', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetCompatibleAudioDecoderConfigurations )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetCompatibleAudioDecoderConfigurations )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub SetVideoSourceConfiguration { - my ($self, $body, $header) = @_; - die "SetVideoSourceConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'SetVideoSourceConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetVideoSourceConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "SetVideoSourceConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'SetVideoSourceConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetVideoSourceConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::SetVideoSourceConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::SetVideoSourceConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub SetVideoEncoderConfiguration { - my ($self, $body, $header) = @_; - die "SetVideoEncoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'SetVideoEncoderConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetVideoEncoderConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "SetVideoEncoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'SetVideoEncoderConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetVideoEncoderConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::SetVideoEncoderConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::SetVideoEncoderConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub SetAudioSourceConfiguration { - my ($self, $body, $header) = @_; - die "SetAudioSourceConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'SetAudioSourceConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetAudioSourceConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "SetAudioSourceConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'SetAudioSourceConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetAudioSourceConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::SetAudioSourceConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::SetAudioSourceConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub SetAudioEncoderConfiguration { - my ($self, $body, $header) = @_; - die "SetAudioEncoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'SetAudioEncoderConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetAudioEncoderConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "SetAudioEncoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'SetAudioEncoderConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetAudioEncoderConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::SetAudioEncoderConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::SetAudioEncoderConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub SetVideoAnalyticsConfiguration { - my ($self, $body, $header) = @_; - die "SetVideoAnalyticsConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'SetVideoAnalyticsConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetVideoAnalyticsConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "SetVideoAnalyticsConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'SetVideoAnalyticsConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetVideoAnalyticsConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::SetVideoAnalyticsConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::SetVideoAnalyticsConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub SetMetadataConfiguration { - my ($self, $body, $header) = @_; - die "SetMetadataConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'SetMetadataConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetMetadataConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "SetMetadataConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'SetMetadataConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetMetadataConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::SetMetadataConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::SetMetadataConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub SetAudioOutputConfiguration { - my ($self, $body, $header) = @_; - die "SetAudioOutputConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'SetAudioOutputConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetAudioOutputConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "SetAudioOutputConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'SetAudioOutputConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetAudioOutputConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::SetAudioOutputConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::SetAudioOutputConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub SetAudioDecoderConfiguration { - my ($self, $body, $header) = @_; - die "SetAudioDecoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'SetAudioDecoderConfiguration', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetAudioDecoderConfiguration', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "SetAudioDecoderConfiguration must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'SetAudioDecoderConfiguration', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetAudioDecoderConfiguration', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::SetAudioDecoderConfiguration )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::SetAudioDecoderConfiguration )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetVideoSourceConfigurationOptions { - my ($self, $body, $header) = @_; - die "GetVideoSourceConfigurationOptions must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetVideoSourceConfigurationOptions', - soap_action => 'http://www.onvif.org/ver10/media/wsdlGetVideoSourceConfigurationOptions/', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetVideoSourceConfigurationOptions must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetVideoSourceConfigurationOptions', + soap_action => 'http://www.onvif.org/ver10/media/wsdlGetVideoSourceConfigurationOptions/', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetVideoSourceConfigurationOptions )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetVideoSourceConfigurationOptions )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetVideoEncoderConfigurationOptions { - my ($self, $body, $header) = @_; - die "GetVideoEncoderConfigurationOptions must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetVideoEncoderConfigurationOptions', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetVideoEncoderConfigurationOptions', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetVideoEncoderConfigurationOptions must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetVideoEncoderConfigurationOptions', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetVideoEncoderConfigurationOptions', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetVideoEncoderConfigurationOptions )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetVideoEncoderConfigurationOptions )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetAudioSourceConfigurationOptions { - my ($self, $body, $header) = @_; - die "GetAudioSourceConfigurationOptions must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetAudioSourceConfigurationOptions', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioSourceConfigurationOptions', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetAudioSourceConfigurationOptions must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetAudioSourceConfigurationOptions', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioSourceConfigurationOptions', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetAudioSourceConfigurationOptions )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetAudioSourceConfigurationOptions )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetAudioEncoderConfigurationOptions { - my ($self, $body, $header) = @_; - die "GetAudioEncoderConfigurationOptions must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetAudioEncoderConfigurationOptions', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioEncoderConfigurationOptions', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetAudioEncoderConfigurationOptions must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetAudioEncoderConfigurationOptions', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioEncoderConfigurationOptions', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetAudioEncoderConfigurationOptions )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetAudioEncoderConfigurationOptions )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetMetadataConfigurationOptions { - my ($self, $body, $header) = @_; - die "GetMetadataConfigurationOptions must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetMetadataConfigurationOptions', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetMetadataConfigurationOptions', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetMetadataConfigurationOptions must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetMetadataConfigurationOptions', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetMetadataConfigurationOptions', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetMetadataConfigurationOptions )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetMetadataConfigurationOptions )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetAudioOutputConfigurationOptions { - my ($self, $body, $header) = @_; - die "GetAudioOutputConfigurationOptions must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetAudioOutputConfigurationOptions', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioOutputConfigurationOptions', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetAudioOutputConfigurationOptions must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetAudioOutputConfigurationOptions', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioOutputConfigurationOptions', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetAudioOutputConfigurationOptions )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetAudioOutputConfigurationOptions )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetAudioDecoderConfigurationOptions { - my ($self, $body, $header) = @_; - die "GetAudioDecoderConfigurationOptions must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetAudioDecoderConfigurationOptions', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioDecoderConfigurationOptions', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetAudioDecoderConfigurationOptions must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetAudioDecoderConfigurationOptions', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetAudioDecoderConfigurationOptions', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetAudioDecoderConfigurationOptions )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetAudioDecoderConfigurationOptions )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetGuaranteedNumberOfVideoEncoderInstances { - my ($self, $body, $header) = @_; - die "GetGuaranteedNumberOfVideoEncoderInstances must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetGuaranteedNumberOfVideoEncoderInstances', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetGuaranteedNumberOfVideoEncoderInstances', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetGuaranteedNumberOfVideoEncoderInstances must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetGuaranteedNumberOfVideoEncoderInstances', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetGuaranteedNumberOfVideoEncoderInstances', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetGuaranteedNumberOfVideoEncoderInstances )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetGuaranteedNumberOfVideoEncoderInstances )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetStreamUri { - my ($self, $body, $header) = @_; - die "GetStreamUri must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetStreamUri', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetStreamUri', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetStreamUri must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetStreamUri', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetStreamUri', + style => 'document', + body => { + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetStreamUri )], + }, + header => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetStreamUri )], - }, - header => { - - }, - headerfault => { - - } + }, + headerfault => { + + } }, $body, $header); } sub StartMulticastStreaming { - my ($self, $body, $header) = @_; - die "StartMulticastStreaming must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'StartMulticastStreaming', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/StartMulticastStreaming', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "StartMulticastStreaming must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'StartMulticastStreaming', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/StartMulticastStreaming', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::StartMulticastStreaming )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::StartMulticastStreaming )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub StopMulticastStreaming { - my ($self, $body, $header) = @_; - die "StopMulticastStreaming must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'StopMulticastStreaming', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/StopMulticastStreaming', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "StopMulticastStreaming must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'StopMulticastStreaming', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/StopMulticastStreaming', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::StopMulticastStreaming )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::StopMulticastStreaming )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub SetSynchronizationPoint { - my ($self, $body, $header) = @_; - die "SetSynchronizationPoint must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'SetSynchronizationPoint', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetSynchronizationPoint', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "SetSynchronizationPoint must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'SetSynchronizationPoint', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetSynchronizationPoint', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::SetSynchronizationPoint )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::SetSynchronizationPoint )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetSnapshotUri { - my ($self, $body, $header) = @_; - die "GetSnapshotUri must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetSnapshotUri', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetSnapshotUri', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetSnapshotUri must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetSnapshotUri', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetSnapshotUri', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetSnapshotUri )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetSnapshotUri )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetVideoSourceModes { - my ($self, $body, $header) = @_; - die "GetVideoSourceModes must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetVideoSourceModes', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetVideoSourceModes', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetVideoSourceModes must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetVideoSourceModes', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetVideoSourceModes', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetVideoSourceModes )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetVideoSourceModes )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub SetVideoSourceMode { - my ($self, $body, $header) = @_; - die "SetVideoSourceMode must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'SetVideoSourceMode', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetVideoSourceMode', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "SetVideoSourceMode must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'SetVideoSourceMode', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetVideoSourceMode', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::SetVideoSourceMode )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::SetVideoSourceMode )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetOSDs { - my ($self, $body, $header) = @_; - die "GetOSDs must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetOSDs', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetOSDs', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetOSDs must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetOSDs', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetOSDs', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetOSDs )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetOSDs )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetOSD { - my ($self, $body, $header) = @_; - die "GetOSD must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetOSD', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetOSD', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetOSD must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetOSD', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetOSD', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetOSD )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetOSD )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub GetOSDOptions { - my ($self, $body, $header) = @_; - die "GetOSDOptions must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetOSDOptions', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetOSDOptions', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetOSDOptions must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetOSDOptions', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/GetOSDOptions', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::GetOSDOptions )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::GetOSDOptions )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub SetOSD { - my ($self, $body, $header) = @_; - die "SetOSD must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'SetOSD', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetOSD', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "SetOSD must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'SetOSD', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/SetOSD', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::SetOSD )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::SetOSD )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub CreateOSD { - my ($self, $body, $header) = @_; - die "CreateOSD must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'CreateOSD', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/CreateOSD', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "CreateOSD must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'CreateOSD', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/CreateOSD', + style => 'document', + body => { - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::CreateOSD )], - }, - header => { - - }, - headerfault => { - - } + + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::CreateOSD )], + }, + header => { + + }, + headerfault => { + + } }, $body, $header); } sub DeleteOSD { - my ($self, $body, $header) = @_; - die "DeleteOSD must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'DeleteOSD', - soap_action => 'http://www.onvif.org/ver10/media/wsdl/DeleteOSD', - style => 'document', - body => { - - - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( ONVIF::Media::Elements::DeleteOSD )], - }, - header => { - - }, - headerfault => { - - } + my ($self, $body, $header) = @_; + die "DeleteOSD must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'DeleteOSD', + soap_action => 'http://www.onvif.org/ver10/media/wsdl/DeleteOSD', + style => 'document', + body => { + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( ONVIF::Media::Elements::DeleteOSD )], + }, + header => { + }, + headerfault => { + } }, $body, $header); } - - - 1; - - - __END__ =pod @@ -2179,7 +2147,7 @@ Returns a 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/Attributes/Id.pm b/onvif/proxy/lib/WSDiscovery11/Attributes/Id.pm new file mode 100644 index 000000000..56d5115e4 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Attributes/Id.pm @@ -0,0 +1,54 @@ + +package WSDiscovery::Attributes::Id; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' } + +__PACKAGE__->__set_name('Id'); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Attribute + SOAP::WSDL::XSD::Typelib::Builtin::ID +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Attributes::Id + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined attribute +Id from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Attributes::Id->new($data); + +Constructor. The following data structure may be passed to new(): + + { value => $value } + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Attributes/IsReferenceParameter.pm b/onvif/proxy/lib/WSDiscovery11/Attributes/IsReferenceParameter.pm new file mode 100644 index 000000000..c04039b1f --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Attributes/IsReferenceParameter.pm @@ -0,0 +1,54 @@ + +package WSDiscovery::Attributes::IsReferenceParameter; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' } + +__PACKAGE__->__set_name('IsReferenceParameter'); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Attribute + SOAP::WSDL::XSD::Typelib::Builtin::boolean +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Attributes::IsReferenceParameter + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined attribute +IsReferenceParameter from the namespace http://www.w3.org/2005/08/addressing. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Attributes::IsReferenceParameter->new($data); + +Constructor. The following data structure may be passed to new(): + + { value => $value } + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/Action.pm b/onvif/proxy/lib/WSDiscovery11/Elements/Action.pm new file mode 100644 index 000000000..a24d6fdd9 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/Action.pm @@ -0,0 +1,57 @@ + +package WSDiscovery::Elements::Action; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' } + +__PACKAGE__->__set_name('Action'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::AttributedURIType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::Action + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +Action from the namespace http://www.w3.org/2005/08/addressing. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::Action->new($data); + +Constructor. The following data structure may be passed to new(): + + { value => $some_value }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/AppSequence.pm b/onvif/proxy/lib/WSDiscovery11/Elements/AppSequence.pm new file mode 100644 index 000000000..63be6d678 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/AppSequence.pm @@ -0,0 +1,58 @@ + +package WSDiscovery::Elements::AppSequence; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' } + +__PACKAGE__->__set_name('AppSequence'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::AppSequenceType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::AppSequence + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +AppSequence from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::AppSequence->new($data); + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::AppSequenceType + }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/Bye.pm b/onvif/proxy/lib/WSDiscovery11/Elements/Bye.pm new file mode 100644 index 000000000..a89c3b48c --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/Bye.pm @@ -0,0 +1,69 @@ + +package WSDiscovery::Elements::Bye; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' } + +__PACKAGE__->__set_name('Bye'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::ByeType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::Bye + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +Bye from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::Bye->new($data); + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::ByeType + EndpointReference => { # WSDiscovery::Types::EndpointReferenceType + Address => { value => $some_value }, + ReferenceParameters => { # WSDiscovery::Types::ReferenceParametersType + }, + Metadata => { # WSDiscovery::Types::MetadataType + }, + }, + Types => $some_value, # QNameListType + Scopes => { value => $some_value }, + XAddrs => $some_value, # UriListType + MetadataVersion => $some_value, # unsignedInt + }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/EndpointReference.pm b/onvif/proxy/lib/WSDiscovery11/Elements/EndpointReference.pm new file mode 100644 index 000000000..96c690bb4 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/EndpointReference.pm @@ -0,0 +1,63 @@ + +package WSDiscovery::Elements::EndpointReference; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' } + +__PACKAGE__->__set_name('EndpointReference'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::EndpointReferenceType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::EndpointReference + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +EndpointReference from the namespace http://www.w3.org/2005/08/addressing. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::EndpointReference->new($data); + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::EndpointReferenceType + Address => { value => $some_value }, + ReferenceParameters => { # WSDiscovery::Types::ReferenceParametersType + }, + Metadata => { # WSDiscovery::Types::MetadataType + }, + }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/FaultTo.pm b/onvif/proxy/lib/WSDiscovery11/Elements/FaultTo.pm new file mode 100644 index 000000000..530f9a4a4 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/FaultTo.pm @@ -0,0 +1,63 @@ + +package WSDiscovery::Elements::FaultTo; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' } + +__PACKAGE__->__set_name('FaultTo'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::EndpointReferenceType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::FaultTo + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +FaultTo from the namespace http://www.w3.org/2005/08/addressing. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::FaultTo->new($data); + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::EndpointReferenceType + Address => { value => $some_value }, + ReferenceParameters => { # WSDiscovery::Types::ReferenceParametersType + }, + Metadata => { # WSDiscovery::Types::MetadataType + }, + }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/From.pm b/onvif/proxy/lib/WSDiscovery11/Elements/From.pm new file mode 100644 index 000000000..727c6a7ef --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/From.pm @@ -0,0 +1,63 @@ + +package WSDiscovery::Elements::From; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' } + +__PACKAGE__->__set_name('From'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::EndpointReferenceType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::From + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +From from the namespace http://www.w3.org/2005/08/addressing. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::From->new($data); + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::EndpointReferenceType + Address => { value => $some_value }, + ReferenceParameters => { # WSDiscovery::Types::ReferenceParametersType + }, + Metadata => { # WSDiscovery::Types::MetadataType + }, + }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/Hello.pm b/onvif/proxy/lib/WSDiscovery11/Elements/Hello.pm new file mode 100644 index 000000000..894f8482d --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/Hello.pm @@ -0,0 +1,69 @@ + +package WSDiscovery::Elements::Hello; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' } + +__PACKAGE__->__set_name('Hello'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::HelloType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::Hello + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +Hello from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::Hello->new($data); + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::HelloType + EndpointReference => { # WSDiscovery::Types::EndpointReferenceType + Address => { value => $some_value }, + ReferenceParameters => { # WSDiscovery::Types::ReferenceParametersType + }, + Metadata => { # WSDiscovery::Types::MetadataType + }, + }, + Types => $some_value, # QNameListType + Scopes => { value => $some_value }, + XAddrs => $some_value, # UriListType + MetadataVersion => $some_value, # unsignedInt + }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/MessageID.pm b/onvif/proxy/lib/WSDiscovery11/Elements/MessageID.pm new file mode 100644 index 000000000..54dfa6a50 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/MessageID.pm @@ -0,0 +1,57 @@ + +package WSDiscovery::Elements::MessageID; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' } + +__PACKAGE__->__set_name('MessageID'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::AttributedURIType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::MessageID + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +MessageID from the namespace http://www.w3.org/2005/08/addressing. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::MessageID->new($data); + +Constructor. The following data structure may be passed to new(): + + { value => $some_value }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/Metadata.pm b/onvif/proxy/lib/WSDiscovery11/Elements/Metadata.pm new file mode 100644 index 000000000..1567ef36a --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/Metadata.pm @@ -0,0 +1,58 @@ + +package WSDiscovery::Elements::Metadata; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' } + +__PACKAGE__->__set_name('Metadata'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::MetadataType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::Metadata + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +Metadata from the namespace http://www.w3.org/2005/08/addressing. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::Metadata->new($data); + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::MetadataType + }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/MetadataVersion.pm b/onvif/proxy/lib/WSDiscovery11/Elements/MetadataVersion.pm new file mode 100644 index 000000000..acb6c5246 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/MetadataVersion.pm @@ -0,0 +1,57 @@ + +package WSDiscovery::Elements::MetadataVersion; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' } + +__PACKAGE__->__set_name('MetadataVersion'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + SOAP::WSDL::XSD::Typelib::Builtin::unsignedInt +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::MetadataVersion + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +MetadataVersion from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::MetadataVersion->new($data); + +Constructor. The following data structure may be passed to new(): + + $some_value, # unsignedInt + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/Probe.pm b/onvif/proxy/lib/WSDiscovery11/Elements/Probe.pm new file mode 100644 index 000000000..b5b780a24 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/Probe.pm @@ -0,0 +1,60 @@ + +package WSDiscovery::Elements::Probe; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' } + +__PACKAGE__->__set_name('Probe'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::ProbeType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::Probe + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +Probe from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::Probe->new($data); + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::ProbeType + Types => $some_value, # QNameListType + Scopes => { value => $some_value }, + }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/ProbeMatches.pm b/onvif/proxy/lib/WSDiscovery11/Elements/ProbeMatches.pm new file mode 100644 index 000000000..5cf9f8fac --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/ProbeMatches.pm @@ -0,0 +1,71 @@ + +package WSDiscovery::Elements::ProbeMatches; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' } + +__PACKAGE__->__set_name('ProbeMatches'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::ProbeMatchesType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::ProbeMatches + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +ProbeMatches from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::ProbeMatches->new($data); + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::ProbeMatchesType + ProbeMatch => { # WSDiscovery::Types::ProbeMatchType + EndpointReference => { # WSDiscovery::Types::EndpointReferenceType + Address => { value => $some_value }, + ReferenceParameters => { # WSDiscovery::Types::ReferenceParametersType + }, + Metadata => { # WSDiscovery::Types::MetadataType + }, + }, + Types => $some_value, # QNameListType + Scopes => { value => $some_value }, + XAddrs => $some_value, # UriListType + MetadataVersion => $some_value, # unsignedInt + }, + }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/ProblemAction.pm b/onvif/proxy/lib/WSDiscovery11/Elements/ProblemAction.pm new file mode 100644 index 000000000..2512b7e91 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/ProblemAction.pm @@ -0,0 +1,60 @@ + +package WSDiscovery::Elements::ProblemAction; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' } + +__PACKAGE__->__set_name('ProblemAction'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::ProblemActionType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::ProblemAction + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +ProblemAction from the namespace http://www.w3.org/2005/08/addressing. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::ProblemAction->new($data); + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::ProblemActionType + Action => { value => $some_value }, + SoapAction => $some_value, # anyURI + }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/ProblemHeaderQName.pm b/onvif/proxy/lib/WSDiscovery11/Elements/ProblemHeaderQName.pm new file mode 100644 index 000000000..bd6f34982 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/ProblemHeaderQName.pm @@ -0,0 +1,57 @@ + +package WSDiscovery::Elements::ProblemHeaderQName; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' } + +__PACKAGE__->__set_name('ProblemHeaderQName'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::AttributedQNameType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::ProblemHeaderQName + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +ProblemHeaderQName from the namespace http://www.w3.org/2005/08/addressing. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::ProblemHeaderQName->new($data); + +Constructor. The following data structure may be passed to new(): + + { value => $some_value }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/ProblemIRI.pm b/onvif/proxy/lib/WSDiscovery11/Elements/ProblemIRI.pm new file mode 100644 index 000000000..70fe19410 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/ProblemIRI.pm @@ -0,0 +1,57 @@ + +package WSDiscovery::Elements::ProblemIRI; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' } + +__PACKAGE__->__set_name('ProblemIRI'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::AttributedURIType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::ProblemIRI + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +ProblemIRI from the namespace http://www.w3.org/2005/08/addressing. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::ProblemIRI->new($data); + +Constructor. The following data structure may be passed to new(): + + { value => $some_value }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/ReferenceParameters.pm b/onvif/proxy/lib/WSDiscovery11/Elements/ReferenceParameters.pm new file mode 100644 index 000000000..1f8cd7cbc --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/ReferenceParameters.pm @@ -0,0 +1,58 @@ + +package WSDiscovery::Elements::ReferenceParameters; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' } + +__PACKAGE__->__set_name('ReferenceParameters'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::ReferenceParametersType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::ReferenceParameters + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +ReferenceParameters from the namespace http://www.w3.org/2005/08/addressing. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::ReferenceParameters->new($data); + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::ReferenceParametersType + }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/RelatesTo.pm b/onvif/proxy/lib/WSDiscovery11/Elements/RelatesTo.pm new file mode 100644 index 000000000..ffdf501aa --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/RelatesTo.pm @@ -0,0 +1,57 @@ + +package WSDiscovery::Elements::RelatesTo; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' } + +__PACKAGE__->__set_name('RelatesTo'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::RelatesToType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::RelatesTo + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +RelatesTo from the namespace http://www.w3.org/2005/08/addressing. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::RelatesTo->new($data); + +Constructor. The following data structure may be passed to new(): + + { value => $some_value }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/ReplyTo.pm b/onvif/proxy/lib/WSDiscovery11/Elements/ReplyTo.pm new file mode 100644 index 000000000..428378c5e --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/ReplyTo.pm @@ -0,0 +1,63 @@ + +package WSDiscovery::Elements::ReplyTo; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' } + +__PACKAGE__->__set_name('ReplyTo'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::EndpointReferenceType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::ReplyTo + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +ReplyTo from the namespace http://www.w3.org/2005/08/addressing. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::ReplyTo->new($data); + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::EndpointReferenceType + Address => { value => $some_value }, + ReferenceParameters => { # WSDiscovery::Types::ReferenceParametersType + }, + Metadata => { # WSDiscovery::Types::MetadataType + }, + }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/Resolve.pm b/onvif/proxy/lib/WSDiscovery11/Elements/Resolve.pm new file mode 100644 index 000000000..57849ce57 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/Resolve.pm @@ -0,0 +1,65 @@ + +package WSDiscovery::Elements::Resolve; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' } + +__PACKAGE__->__set_name('Resolve'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::ResolveType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::Resolve + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +Resolve from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::Resolve->new($data); + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::ResolveType + EndpointReference => { # WSDiscovery::Types::EndpointReferenceType + Address => { value => $some_value }, + ReferenceParameters => { # WSDiscovery::Types::ReferenceParametersType + }, + Metadata => { # WSDiscovery::Types::MetadataType + }, + }, + }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/ResolveMatches.pm b/onvif/proxy/lib/WSDiscovery11/Elements/ResolveMatches.pm new file mode 100644 index 000000000..cfaabc989 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/ResolveMatches.pm @@ -0,0 +1,71 @@ + +package WSDiscovery::Elements::ResolveMatches; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' } + +__PACKAGE__->__set_name('ResolveMatches'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::ResolveMatchesType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::ResolveMatches + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +ResolveMatches from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::ResolveMatches->new($data); + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::ResolveMatchesType + ResolveMatch => { # WSDiscovery::Types::ResolveMatchType + EndpointReference => { # WSDiscovery::Types::EndpointReferenceType + Address => { value => $some_value }, + ReferenceParameters => { # WSDiscovery::Types::ReferenceParametersType + }, + Metadata => { # WSDiscovery::Types::MetadataType + }, + }, + Types => $some_value, # QNameListType + Scopes => { value => $some_value }, + XAddrs => $some_value, # UriListType + MetadataVersion => $some_value, # unsignedInt + }, + }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/RetryAfter.pm b/onvif/proxy/lib/WSDiscovery11/Elements/RetryAfter.pm new file mode 100644 index 000000000..980eeab2d --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/RetryAfter.pm @@ -0,0 +1,57 @@ + +package WSDiscovery::Elements::RetryAfter; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' } + +__PACKAGE__->__set_name('RetryAfter'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::AttributedUnsignedLongType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::RetryAfter + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +RetryAfter from the namespace http://www.w3.org/2005/08/addressing. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::RetryAfter->new($data); + +Constructor. The following data structure may be passed to new(): + + { value => $some_value }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/Scopes.pm b/onvif/proxy/lib/WSDiscovery11/Elements/Scopes.pm new file mode 100644 index 000000000..1dde55ce1 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/Scopes.pm @@ -0,0 +1,57 @@ + +package WSDiscovery::Elements::Scopes; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' } + +__PACKAGE__->__set_name('Scopes'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::ScopesType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::Scopes + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +Scopes from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::Scopes->new($data); + +Constructor. The following data structure may be passed to new(): + + { value => $some_value }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/Security.pm b/onvif/proxy/lib/WSDiscovery11/Elements/Security.pm new file mode 100644 index 000000000..f9d4aff4d --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/Security.pm @@ -0,0 +1,60 @@ + +package WSDiscovery::Elements::Security; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' } + +__PACKAGE__->__set_name('Security'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::SecurityType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::Security + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +Security from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::Security->new($data); + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::SecurityType + Sig => { # WSDiscovery::Types::SigType + }, + }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/Sig.pm b/onvif/proxy/lib/WSDiscovery11/Elements/Sig.pm new file mode 100644 index 000000000..6ddab0bed --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/Sig.pm @@ -0,0 +1,58 @@ + +package WSDiscovery::Elements::Sig; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' } + +__PACKAGE__->__set_name('Sig'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::SigType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::Sig + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +Sig from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::Sig->new($data); + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::SigType + }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/SupportedMatchingRules.pm b/onvif/proxy/lib/WSDiscovery11/Elements/SupportedMatchingRules.pm new file mode 100644 index 000000000..908566429 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/SupportedMatchingRules.pm @@ -0,0 +1,57 @@ + +package WSDiscovery::Elements::SupportedMatchingRules; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' } + +__PACKAGE__->__set_name('SupportedMatchingRules'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::UriListType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::SupportedMatchingRules + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +SupportedMatchingRules from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::SupportedMatchingRules->new($data); + +Constructor. The following data structure may be passed to new(): + +$some_value, # UriListType + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/To.pm b/onvif/proxy/lib/WSDiscovery11/Elements/To.pm new file mode 100644 index 000000000..d435b934d --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/To.pm @@ -0,0 +1,57 @@ + +package WSDiscovery::Elements::To; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' } + +__PACKAGE__->__set_name('To'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::AttributedURIType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::To + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +To from the namespace http://www.w3.org/2005/08/addressing. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::To->new($data); + +Constructor. The following data structure may be passed to new(): + + { value => $some_value }, + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/Types.pm b/onvif/proxy/lib/WSDiscovery11/Elements/Types.pm new file mode 100644 index 000000000..15667a3a0 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/Types.pm @@ -0,0 +1,57 @@ + +package WSDiscovery::Elements::Types; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' } + +__PACKAGE__->__set_name('Types'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::QNameListType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::Types + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +Types from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::Types->new($data); + +Constructor. The following data structure may be passed to new(): + +$some_value, # QNameListType + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Elements/XAddrs.pm b/onvif/proxy/lib/WSDiscovery11/Elements/XAddrs.pm new file mode 100644 index 000000000..bc0c8d998 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Elements/XAddrs.pm @@ -0,0 +1,57 @@ + +package WSDiscovery::Elements::XAddrs; +use strict; +use warnings; + +{ # BLOCK to scope variables + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' } + +__PACKAGE__->__set_name('XAddrs'); +__PACKAGE__->__set_nillable(); +__PACKAGE__->__set_minOccurs(); +__PACKAGE__->__set_maxOccurs(); +__PACKAGE__->__set_ref(); +use base qw( + SOAP::WSDL::XSD::Typelib::Element + WSDiscovery::Types::UriListType +); + +} + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Elements::XAddrs + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined element +XAddrs from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + + +=head1 METHODS + +=head2 new + + my $element = WSDiscovery::Elements::XAddrs->new($data); + +Constructor. The following data structure may be passed to new(): + +$some_value, # UriListType + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Interfaces/WSDiscovery/WSDiscoveryPort.pm b/onvif/proxy/lib/WSDiscovery11/Interfaces/WSDiscovery/WSDiscoveryPort.pm new file mode 100644 index 000000000..0aa2a393c --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Interfaces/WSDiscovery/WSDiscoveryPort.pm @@ -0,0 +1,131 @@ +package WSDiscovery::Interfaces::WSDiscovery::WSDiscoveryPort; +use strict; +use warnings; +use Class::Std::Fast::Storable; +use Scalar::Util qw(blessed); +use base qw(SOAP::WSDL::Client::Base); + +# only load if it hasn't been loaded before +require WSDiscovery::Typemaps::WSDiscovery + if not WSDiscovery::Typemaps::WSDiscovery->can('get_class'); + +sub START { + $_[0]->set_proxy('soap.udp://239.255.255.250:3702/') if not $_[2]->{proxy}; + $_[0]->set_class_resolver('WSDiscovery::Typemaps::WSDiscovery') + if not $_[2]->{class_resolver}; + + $_[0]->set_prefix($_[2]->{use_prefix}) if exists $_[2]->{use_prefix}; +} + +sub ProbeOp { + my ($self, $body, $header) = @_; + die "ProbeOp must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'ProbeOp', + soap_action => 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Probe', + style => 'document', + body => { + + + 'use' => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( WSDiscovery::Elements::Probe )], + + }, + header => { + + }, + headerfault => { + + } + }, $body, $header); +} + + + + +1; + + + +__END__ + +=pod + +=head1 NAME + +WSDiscovery::Interfaces::WSDiscovery::WSDiscoveryPort - SOAP Interface for the WSDiscovery Web Service + +=head1 SYNOPSIS + + use WSDiscovery::Interfaces::WSDiscovery::WSDiscoveryPort; + my $interface = WSDiscovery::Interfaces::WSDiscovery::WSDiscoveryPort->new(); + + my $response; + $response = $interface->ProbeOp(); + + + +=head1 DESCRIPTION + +SOAP Interface for the WSDiscovery web service +located at soap.udp://239.255.255.250:3702/. + +=head1 SERVICE WSDiscovery + + + +=head2 Port WSDiscoveryPort + + + +=head1 METHODS + +=head2 General methods + +=head3 new + +Constructor. + +All arguments are forwarded to L. + +=head2 SOAP Service methods + +Method synopsis is displayed with hash refs as parameters. + +The commented class names in the method's parameters denote that objects +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 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. + +XML attributes are not displayed in this synopsis and cannot be set using +hash refs. See the respective class' documentation for additional information. + + + +=head3 ProbeOp + + + +Returns a L object. + + $response = $interface->ProbeOp( { # WSDiscovery::Types::ProbeType + Types => $some_value, # QNameListType + Scopes => { value => $some_value }, + },, + ); + + + +=head1 AUTHOR + +Generated by SOAP::WSDL on Wed Jul 2 11:45:24 2014 + +=cut diff --git a/onvif/proxy/lib/WSDiscovery11/Typemaps/WSDiscovery.pm b/onvif/proxy/lib/WSDiscovery11/Typemaps/WSDiscovery.pm new file mode 100644 index 000000000..136550e96 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Typemaps/WSDiscovery.pm @@ -0,0 +1,59 @@ + +package WSDiscovery::Typemaps::WSDiscovery; +use strict; +use warnings; + +our $typemap_1 = { + 'ProbeMatches/ProbeMatch/XAddrs' => 'WSDiscovery::Types::UriListType', + 'ProbeMatches/ProbeMatch' => 'WSDiscovery::Types::ProbeMatchType', + 'Fault/detail' => 'SOAP::WSDL::XSD::Typelib::Builtin::string', + 'ProbeMatches/ProbeMatch/EndpointReference/Metadata' => 'WSDiscovery::Types::MetadataType', + 'ProbeMatches/ProbeMatch/Scopes' => 'WSDiscovery::Types::ScopesType', + 'ProbeMatches/ProbeMatch/EndpointReference/Address' => 'WSDiscovery::Types::AttributedURIType', + 'Probe/Types' => 'WSDiscovery::Types::QNameListType', + 'Probe' => 'WSDiscovery::Elements::Probe', + 'Fault/faultstring' => 'SOAP::WSDL::XSD::Typelib::Builtin::string', + 'ProbeMatches/ProbeMatch/MetadataVersion' => 'SOAP::WSDL::XSD::Typelib::Builtin::unsignedInt', + 'Probe/Scopes' => 'WSDiscovery::Types::ScopesType', + 'Fault/faultactor' => 'SOAP::WSDL::XSD::Typelib::Builtin::token', + 'Fault/faultcode' => 'SOAP::WSDL::XSD::Typelib::Builtin::anyURI', + 'ProbeMatches/ProbeMatch/Types' => 'WSDiscovery::Types::QNameListType', + 'ProbeMatches/ProbeMatch/EndpointReference/ReferenceParameters' => 'WSDiscovery::Types::ReferenceParametersType', + 'ProbeMatches/ProbeMatch/EndpointReference' => 'WSDiscovery::Types::EndpointReferenceType', + 'Fault' => 'SOAP::WSDL::SOAP::Typelib::Fault11', + 'ProbeMatches' => 'WSDiscovery::Elements::ProbeMatches', + 'MessageID' => 'WSDiscovery::Elements::MessageID', + 'RelatesTo' => '__SKIP__', + 'To' => '__SKIP__', + 'Action' => '__SKIP__', + 'AppSequence' => '__SKIP__', + }; +; + +sub get_class { + my $name = join '/', @{ $_[1] }; + return $typemap_1->{ $name }; +} + +sub get_typemap { + return $typemap_1; +} + +1; + +__END__ + +__END__ + +=pod + +=head1 NAME + +WSDiscovery::Typemaps::WSDiscovery - typemap for WSDiscovery + +=head1 DESCRIPTION + +Typemap created by SOAP::WSDL for map-based SOAP message parsers. + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/AppSequenceType.pm b/onvif/proxy/lib/WSDiscovery11/Types/AppSequenceType.pm new file mode 100644 index 000000000..18e8ea8c3 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/AppSequenceType.pm @@ -0,0 +1,138 @@ +package WSDiscovery::Types::AppSequenceType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' }; + +our $XML_ATTRIBUTE_CLASS = 'WSDiscovery::Types::AppSequenceType::_AppSequenceType::XmlAttr'; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use base qw(); + +package WSDiscovery::Types::AppSequenceType::_AppSequenceType::XmlAttr; +use base qw(SOAP::WSDL::XSD::Typelib::AttributeSet); + +{ # BLOCK to scope variables + +my %InstanceId_of :ATTR(:get); +my %SequenceId_of :ATTR(:get); +my %MessageNumber_of :ATTR(:get); + +__PACKAGE__->_factory( + [ qw( + InstanceId + SequenceId + MessageNumber + ) ], + { + + InstanceId => \%InstanceId_of, + + SequenceId => \%SequenceId_of, + + MessageNumber => \%MessageNumber_of, + }, + { + InstanceId => 'SOAP::WSDL::XSD::Typelib::Builtin::unsignedInt', + SequenceId => 'SOAP::WSDL::XSD::Typelib::Builtin::anyURI', + MessageNumber => 'SOAP::WSDL::XSD::Typelib::Builtin::unsignedInt', + } +); + +} # end BLOCK + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::AppSequenceType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +AppSequenceType from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::AppSequenceType + }, + + + +=head2 attr + +NOTE: Attribute documentation is experimental, and may be inaccurate. +See the correspondent WSDL/XML Schema if in question. + +This class has additional attributes, accessibly via the C method. + +attr() returns an object of the class WSDiscovery::Types::AppSequenceType::_AppSequenceType::XmlAttr. + +The following attributes can be accessed on this object via the corresponding +get_/set_ methods: + +=over + +=item * InstanceId + + + +This attribute is of type L. + +=item * SequenceId + + + +This attribute is of type L. + +=item * MessageNumber + + + +This attribute is of type L. + + +=back + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/AttributedQNameType.pm b/onvif/proxy/lib/WSDiscovery11/Types/AttributedQNameType.pm new file mode 100644 index 000000000..dd2d1522c --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/AttributedQNameType.pm @@ -0,0 +1,73 @@ +package WSDiscovery::Types::AttributedQNameType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' }; + +our $XML_ATTRIBUTE_CLASS; +undef $XML_ATTRIBUTE_CLASS; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use base qw( + SOAP::WSDL::XSD::Typelib::ComplexType + SOAP::WSDL::XSD::Typelib::Builtin::QName +); + + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::AttributedQNameType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +AttributedQNameType from the namespace http://www.w3.org/2005/08/addressing. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { value => $some_value }, + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/AttributedURIType.pm b/onvif/proxy/lib/WSDiscovery11/Types/AttributedURIType.pm new file mode 100644 index 000000000..5cab1a8c9 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/AttributedURIType.pm @@ -0,0 +1,73 @@ +package WSDiscovery::Types::AttributedURIType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' }; + +our $XML_ATTRIBUTE_CLASS; +undef $XML_ATTRIBUTE_CLASS; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use base qw( + SOAP::WSDL::XSD::Typelib::ComplexType + SOAP::WSDL::XSD::Typelib::Builtin::anyURI +); + + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::AttributedURIType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +AttributedURIType from the namespace http://www.w3.org/2005/08/addressing. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { value => $some_value }, + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/AttributedUnsignedLongType.pm b/onvif/proxy/lib/WSDiscovery11/Types/AttributedUnsignedLongType.pm new file mode 100644 index 000000000..c2a947795 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/AttributedUnsignedLongType.pm @@ -0,0 +1,73 @@ +package WSDiscovery::Types::AttributedUnsignedLongType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' }; + +our $XML_ATTRIBUTE_CLASS; +undef $XML_ATTRIBUTE_CLASS; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use base qw( + SOAP::WSDL::XSD::Typelib::ComplexType + SOAP::WSDL::XSD::Typelib::Builtin::unsignedLong +); + + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::AttributedUnsignedLongType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +AttributedUnsignedLongType from the namespace http://www.w3.org/2005/08/addressing. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { value => $some_value }, + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/ByeType.pm b/onvif/proxy/lib/WSDiscovery11/Types/ByeType.pm new file mode 100644 index 000000000..9c384fb2a --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/ByeType.pm @@ -0,0 +1,180 @@ +package WSDiscovery::Types::ByeType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' }; + +our $XML_ATTRIBUTE_CLASS; +undef $XML_ATTRIBUTE_CLASS; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use Class::Std::Fast::Storable constructor => 'none'; +use base qw(SOAP::WSDL::XSD::Typelib::ComplexType); + +Class::Std::initialize(); + +{ # BLOCK to scope variables + +my %EndpointReference_of :ATTR(:get); +my %Types_of :ATTR(:get); +my %Scopes_of :ATTR(:get); +my %XAddrs_of :ATTR(:get); +my %MetadataVersion_of :ATTR(:get); + +__PACKAGE__->_factory( + [ qw( EndpointReference + Types + Scopes + XAddrs + MetadataVersion + + ) ], + { + 'EndpointReference' => \%EndpointReference_of, + 'Types' => \%Types_of, + 'Scopes' => \%Scopes_of, + 'XAddrs' => \%XAddrs_of, + 'MetadataVersion' => \%MetadataVersion_of, + }, + { + 'EndpointReference' => 'WSDiscovery::Elements::EndpointReference', + + 'Types' => 'WSDiscovery::Elements::Types', + + 'Scopes' => 'WSDiscovery::Elements::Scopes', + + 'XAddrs' => 'WSDiscovery::Elements::XAddrs', + + 'MetadataVersion' => 'WSDiscovery::Elements::MetadataVersion', + + }, + { + + 'EndpointReference' => '', + 'Types' => '', + 'Scopes' => '', + 'XAddrs' => '', + 'MetadataVersion' => '', + } +); + +} # end BLOCK + + + + + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::ByeType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +ByeType from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + +=item * EndpointReference + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * Types + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * Scopes + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * XAddrs + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * MetadataVersion + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::ByeType + EndpointReference => { # WSDiscovery::Types::EndpointReferenceType + Address => { value => $some_value }, + ReferenceParameters => { # WSDiscovery::Types::ReferenceParametersType + }, + Metadata => { # WSDiscovery::Types::MetadataType + }, + }, + Types => $some_value, # QNameListType + Scopes => { value => $some_value }, + XAddrs => $some_value, # UriListType + MetadataVersion => $some_value, # unsignedInt + }, + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/EndpointReferenceType.pm b/onvif/proxy/lib/WSDiscovery11/Types/EndpointReferenceType.pm new file mode 100644 index 000000000..59411fa37 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/EndpointReferenceType.pm @@ -0,0 +1,137 @@ +package WSDiscovery::Types::EndpointReferenceType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' }; + +our $XML_ATTRIBUTE_CLASS; +undef $XML_ATTRIBUTE_CLASS; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use Class::Std::Fast::Storable constructor => 'none'; +use base qw(SOAP::WSDL::XSD::Typelib::ComplexType); + +Class::Std::initialize(); + +{ # BLOCK to scope variables + +my %Address_of :ATTR(:get
); +my %ReferenceParameters_of :ATTR(:get); +my %Metadata_of :ATTR(:get); + +__PACKAGE__->_factory( + [ qw( Address + ReferenceParameters + Metadata + + ) ], + { + 'Address' => \%Address_of, + 'ReferenceParameters' => \%ReferenceParameters_of, + 'Metadata' => \%Metadata_of, + }, + { + 'Address' => 'WSDiscovery::Types::AttributedURIType', + 'ReferenceParameters' => 'WSDiscovery::Elements::ReferenceParameters', + + 'Metadata' => 'WSDiscovery::Elements::Metadata', + + }, + { + + 'Address' => 'Address', + 'ReferenceParameters' => '', + 'Metadata' => '', + } +); + +} # end BLOCK + + + + + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::EndpointReferenceType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +EndpointReferenceType from the namespace http://www.w3.org/2005/08/addressing. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + +=item * Address + + +=item * ReferenceParameters + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * Metadata + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::EndpointReferenceType + Address => { value => $some_value }, + ReferenceParameters => { # WSDiscovery::Types::ReferenceParametersType + }, + Metadata => { # WSDiscovery::Types::MetadataType + }, + }, + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/FaultCodeOpenType.pm b/onvif/proxy/lib/WSDiscovery11/Types/FaultCodeOpenType.pm new file mode 100644 index 000000000..42fe97b83 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/FaultCodeOpenType.pm @@ -0,0 +1,76 @@ +package WSDiscovery::Types::FaultCodeOpenType; +use strict; +use warnings; + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01'}; + +# derivation by union +# union is not fully supported yet - value space constraints are not +# checked yet. +# This implementation of union resorts to the simplest possible base, which +# is: "If the or alternative is chosen, then the +# simple ur-type definition·." +# + +use base qw( + SOAP::WSDL::XSD::Typelib::Builtin::anySimpleType +); + + + +1; + +__END__ + +=pod + +=head1 NAME + + + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined simpleType +FaultCodeOpenType from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + +This type class is derived by union. + +Derivation by union is not fully supported yet - value space constraints are +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." + + + +=head1 METHODS + +=head2 new + +Constructor. + +=head2 get_value / set_value + +Getter and setter for the simpleType's value. + +=head1 OVERLOADING + +Depending on the simple type's base type, the following operations are overloaded + + Stringification + Numerification + Boolification + +Check L for more information. + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/FaultCodeType.pm b/onvif/proxy/lib/WSDiscovery11/Types/FaultCodeType.pm new file mode 100644 index 000000000..4cc63eb8b --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/FaultCodeType.pm @@ -0,0 +1,65 @@ +package WSDiscovery::Types::FaultCodeType; +use strict; +use warnings; + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01'}; + +# derivation by restriction +use base qw( + SOAP::WSDL::XSD::Typelib::Builtin::QName); + + + +1; + +__END__ + +=pod + +=head1 NAME + + + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined simpleType +FaultCodeType from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + +This clase is derived from + SOAP::WSDL::XSD::Typelib::Builtin::QName +. SOAP::WSDL's schema implementation does not validate data, so you can use it exactly +like it's base type. + +# Description of restrictions not implemented yet. + + +=head1 METHODS + +=head2 new + +Constructor. + +=head2 get_value / set_value + +Getter and setter for the simpleType's value. + +=head1 OVERLOADING + +Depending on the simple type's base type, the following operations are overloaded + + Stringification + Numerification + Boolification + +Check L for more information. + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/FaultCodesOpenEnumType.pm b/onvif/proxy/lib/WSDiscovery11/Types/FaultCodesOpenEnumType.pm new file mode 100644 index 000000000..072948d96 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/FaultCodesOpenEnumType.pm @@ -0,0 +1,76 @@ +package WSDiscovery::Types::FaultCodesOpenEnumType; +use strict; +use warnings; + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing'}; + +# derivation by union +# union is not fully supported yet - value space constraints are not +# checked yet. +# This implementation of union resorts to the simplest possible base, which +# is: "If the or alternative is chosen, then the +# simple ur-type definition·." +# + +use base qw( + SOAP::WSDL::XSD::Typelib::Builtin::anySimpleType +); + + + +1; + +__END__ + +=pod + +=head1 NAME + + + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined simpleType +FaultCodesOpenEnumType from the namespace http://www.w3.org/2005/08/addressing. + + + + + +This type class is derived by union. + +Derivation by union is not fully supported yet - value space constraints are +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." + + + +=head1 METHODS + +=head2 new + +Constructor. + +=head2 get_value / set_value + +Getter and setter for the simpleType's value. + +=head1 OVERLOADING + +Depending on the simple type's base type, the following operations are overloaded + + Stringification + Numerification + Boolification + +Check L for more information. + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/FaultCodesType.pm b/onvif/proxy/lib/WSDiscovery11/Types/FaultCodesType.pm new file mode 100644 index 000000000..a042be3cf --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/FaultCodesType.pm @@ -0,0 +1,65 @@ +package WSDiscovery::Types::FaultCodesType; +use strict; +use warnings; + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing'}; + +# derivation by restriction +use base qw( + SOAP::WSDL::XSD::Typelib::Builtin::QName); + + + +1; + +__END__ + +=pod + +=head1 NAME + + + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined simpleType +FaultCodesType from the namespace http://www.w3.org/2005/08/addressing. + + + + + +This clase is derived from + SOAP::WSDL::XSD::Typelib::Builtin::QName +. SOAP::WSDL's schema implementation does not validate data, so you can use it exactly +like it's base type. + +# Description of restrictions not implemented yet. + + +=head1 METHODS + +=head2 new + +Constructor. + +=head2 get_value / set_value + +Getter and setter for the simpleType's value. + +=head1 OVERLOADING + +Depending on the simple type's base type, the following operations are overloaded + + Stringification + Numerification + Boolification + +Check L for more information. + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/HelloType.pm b/onvif/proxy/lib/WSDiscovery11/Types/HelloType.pm new file mode 100644 index 000000000..1842faf6a --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/HelloType.pm @@ -0,0 +1,180 @@ +package WSDiscovery::Types::HelloType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' }; + +our $XML_ATTRIBUTE_CLASS; +undef $XML_ATTRIBUTE_CLASS; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use Class::Std::Fast::Storable constructor => 'none'; +use base qw(SOAP::WSDL::XSD::Typelib::ComplexType); + +Class::Std::initialize(); + +{ # BLOCK to scope variables + +my %EndpointReference_of :ATTR(:get); +my %Types_of :ATTR(:get); +my %Scopes_of :ATTR(:get); +my %XAddrs_of :ATTR(:get); +my %MetadataVersion_of :ATTR(:get); + +__PACKAGE__->_factory( + [ qw( EndpointReference + Types + Scopes + XAddrs + MetadataVersion + + ) ], + { + 'EndpointReference' => \%EndpointReference_of, + 'Types' => \%Types_of, + 'Scopes' => \%Scopes_of, + 'XAddrs' => \%XAddrs_of, + 'MetadataVersion' => \%MetadataVersion_of, + }, + { + 'EndpointReference' => 'WSDiscovery::Elements::EndpointReference', + + 'Types' => 'WSDiscovery::Elements::Types', + + 'Scopes' => 'WSDiscovery::Elements::Scopes', + + 'XAddrs' => 'WSDiscovery::Elements::XAddrs', + + 'MetadataVersion' => 'WSDiscovery::Elements::MetadataVersion', + + }, + { + + 'EndpointReference' => '', + 'Types' => '', + 'Scopes' => '', + 'XAddrs' => '', + 'MetadataVersion' => '', + } +); + +} # end BLOCK + + + + + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::HelloType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +HelloType from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + +=item * EndpointReference + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * Types + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * Scopes + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * XAddrs + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * MetadataVersion + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::HelloType + EndpointReference => { # WSDiscovery::Types::EndpointReferenceType + Address => { value => $some_value }, + ReferenceParameters => { # WSDiscovery::Types::ReferenceParametersType + }, + Metadata => { # WSDiscovery::Types::MetadataType + }, + }, + Types => $some_value, # QNameListType + Scopes => { value => $some_value }, + XAddrs => $some_value, # UriListType + MetadataVersion => $some_value, # unsignedInt + }, + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/MetadataType.pm b/onvif/proxy/lib/WSDiscovery11/Types/MetadataType.pm new file mode 100644 index 000000000..072161f78 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/MetadataType.pm @@ -0,0 +1,94 @@ +package WSDiscovery::Types::MetadataType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' }; + +our $XML_ATTRIBUTE_CLASS; +undef $XML_ATTRIBUTE_CLASS; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use Class::Std::Fast::Storable constructor => 'none'; +use base qw(SOAP::WSDL::XSD::Typelib::ComplexType); + +Class::Std::initialize(); + +{ # BLOCK to scope variables + + +__PACKAGE__->_factory( + [ qw( + ) ], + { + }, + { + }, + { + + } +); + +} # end BLOCK + + + + + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::MetadataType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +MetadataType from the namespace http://www.w3.org/2005/08/addressing. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::MetadataType + }, + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/ProbeMatchType.pm b/onvif/proxy/lib/WSDiscovery11/Types/ProbeMatchType.pm new file mode 100644 index 000000000..c85d98849 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/ProbeMatchType.pm @@ -0,0 +1,180 @@ +package WSDiscovery::Types::ProbeMatchType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' }; + +our $XML_ATTRIBUTE_CLASS; +undef $XML_ATTRIBUTE_CLASS; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use Class::Std::Fast::Storable constructor => 'none'; +use base qw(SOAP::WSDL::XSD::Typelib::ComplexType); + +Class::Std::initialize(); + +{ # BLOCK to scope variables + +my %EndpointReference_of :ATTR(:get); +my %Types_of :ATTR(:get); +my %Scopes_of :ATTR(:get); +my %XAddrs_of :ATTR(:get); +my %MetadataVersion_of :ATTR(:get); + +__PACKAGE__->_factory( + [ qw( EndpointReference + Types + Scopes + XAddrs + MetadataVersion + + ) ], + { + 'EndpointReference' => \%EndpointReference_of, + 'Types' => \%Types_of, + 'Scopes' => \%Scopes_of, + 'XAddrs' => \%XAddrs_of, + 'MetadataVersion' => \%MetadataVersion_of, + }, + { + 'EndpointReference' => 'WSDiscovery::Elements::EndpointReference', + + 'Types' => 'WSDiscovery::Elements::Types', + + 'Scopes' => 'WSDiscovery::Elements::Scopes', + + 'XAddrs' => 'WSDiscovery::Elements::XAddrs', + + 'MetadataVersion' => 'WSDiscovery::Elements::MetadataVersion', + + }, + { + + 'EndpointReference' => '', + 'Types' => '', + 'Scopes' => '', + 'XAddrs' => '', + 'MetadataVersion' => '', + } +); + +} # end BLOCK + + + + + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::ProbeMatchType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +ProbeMatchType from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + +=item * EndpointReference + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * Types + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * Scopes + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * XAddrs + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * MetadataVersion + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::ProbeMatchType + EndpointReference => { # WSDiscovery::Types::EndpointReferenceType + Address => { value => $some_value }, + ReferenceParameters => { # WSDiscovery::Types::ReferenceParametersType + }, + Metadata => { # WSDiscovery::Types::MetadataType + }, + }, + Types => $some_value, # QNameListType + Scopes => { value => $some_value }, + XAddrs => $some_value, # UriListType + MetadataVersion => $some_value, # unsignedInt + }, + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/ProbeMatchesType.pm b/onvif/proxy/lib/WSDiscovery11/Types/ProbeMatchesType.pm new file mode 100644 index 000000000..200edc6a4 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/ProbeMatchesType.pm @@ -0,0 +1,115 @@ +package WSDiscovery::Types::ProbeMatchesType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' }; + +our $XML_ATTRIBUTE_CLASS; +undef $XML_ATTRIBUTE_CLASS; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use Class::Std::Fast::Storable constructor => 'none'; +use base qw(SOAP::WSDL::XSD::Typelib::ComplexType); + +Class::Std::initialize(); + +{ # BLOCK to scope variables + +my %ProbeMatch_of :ATTR(:get); + +__PACKAGE__->_factory( + [ qw( ProbeMatch + + ) ], + { + 'ProbeMatch' => \%ProbeMatch_of, + }, + { + 'ProbeMatch' => 'WSDiscovery::Types::ProbeMatchType', + }, + { + + 'ProbeMatch' => 'ProbeMatch', + } +); + +} # end BLOCK + + + + + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::ProbeMatchesType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +ProbeMatchesType from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + +=item * ProbeMatch + + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::ProbeMatchesType + ProbeMatch => { # WSDiscovery::Types::ProbeMatchType + EndpointReference => { # WSDiscovery::Types::EndpointReferenceType + Address => { value => $some_value }, + ReferenceParameters => { # WSDiscovery::Types::ReferenceParametersType + }, + Metadata => { # WSDiscovery::Types::MetadataType + }, + }, + Types => $some_value, # QNameListType + Scopes => { value => $some_value }, + XAddrs => $some_value, # UriListType + MetadataVersion => $some_value, # unsignedInt + }, + }, + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/ProbeType.pm b/onvif/proxy/lib/WSDiscovery11/Types/ProbeType.pm new file mode 100644 index 000000000..60dfa0bf3 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/ProbeType.pm @@ -0,0 +1,126 @@ +package WSDiscovery::Types::ProbeType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' }; + +our $XML_ATTRIBUTE_CLASS; +undef $XML_ATTRIBUTE_CLASS; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use Class::Std::Fast::Storable constructor => 'none'; +use base qw(SOAP::WSDL::XSD::Typelib::ComplexType); + +Class::Std::initialize(); + +{ # BLOCK to scope variables + +my %Types_of :ATTR(:get); +my %Scopes_of :ATTR(:get); + +__PACKAGE__->_factory( + [ qw( Types + Scopes + + ) ], + { + 'Types' => \%Types_of, + 'Scopes' => \%Scopes_of, + }, + { + 'Types' => 'WSDiscovery::Elements::Types', + + 'Scopes' => 'WSDiscovery::Elements::Scopes', + + }, + { + + 'Types' => '', + 'Scopes' => '', + } +); + +} # end BLOCK + + + + + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::ProbeType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +ProbeType from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + +=item * Types + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * Scopes + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::ProbeType + Types => $some_value, # QNameListType + Scopes => { value => $some_value }, + }, + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/ProblemActionType.pm b/onvif/proxy/lib/WSDiscovery11/Types/ProblemActionType.pm new file mode 100644 index 000000000..1426017f4 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/ProblemActionType.pm @@ -0,0 +1,119 @@ +package WSDiscovery::Types::ProblemActionType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' }; + +our $XML_ATTRIBUTE_CLASS; +undef $XML_ATTRIBUTE_CLASS; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use Class::Std::Fast::Storable constructor => 'none'; +use base qw(SOAP::WSDL::XSD::Typelib::ComplexType); + +Class::Std::initialize(); + +{ # BLOCK to scope variables + +my %Action_of :ATTR(:get); +my %SoapAction_of :ATTR(:get); + +__PACKAGE__->_factory( + [ qw( Action + SoapAction + + ) ], + { + 'Action' => \%Action_of, + 'SoapAction' => \%SoapAction_of, + }, + { + 'Action' => 'WSDiscovery::Elements::Action', + + 'SoapAction' => 'SOAP::WSDL::XSD::Typelib::Builtin::anyURI', + }, + { + + 'Action' => '', + 'SoapAction' => 'SoapAction', + } +); + +} # end BLOCK + + + + + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::ProblemActionType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +ProblemActionType from the namespace http://www.w3.org/2005/08/addressing. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + +=item * Action + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * SoapAction + + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::ProblemActionType + Action => { value => $some_value }, + SoapAction => $some_value, # anyURI + }, + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/QNameListType.pm b/onvif/proxy/lib/WSDiscovery11/Types/QNameListType.pm new file mode 100644 index 000000000..fef443675 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/QNameListType.pm @@ -0,0 +1,75 @@ +package WSDiscovery::Types::QNameListType; +use strict; +use warnings; + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01'}; + + +# list derivation +use base qw( + SOAP::WSDL::XSD::Typelib::Builtin::list + SOAP::WSDL::XSD::Typelib::Builtin::QName +); + + + + + +1; + +__END__ + +=pod + +=head1 NAME + + + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined simpleType +QNameListType from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + +This clase is derived from + SOAP::WSDL::XSD::Typelib::Builtin::QName +. + +You may pass the following structure to new(): + + [ $value_1, .. $value_n ] + +All elements of the list must be of the class' base type (or valid arguments +to it's constructor). + + + +=head1 METHODS + +=head2 new + +Constructor. + +=head2 get_value / set_value + +Getter and setter for the simpleType's value. + +=head1 OVERLOADING + +Depending on the simple type's base type, the following operations are overloaded + + Stringification + Numerification + Boolification + +Check L for more information. + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/ReferenceParametersType.pm b/onvif/proxy/lib/WSDiscovery11/Types/ReferenceParametersType.pm new file mode 100644 index 000000000..d6569a66b --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/ReferenceParametersType.pm @@ -0,0 +1,94 @@ +package WSDiscovery::Types::ReferenceParametersType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' }; + +our $XML_ATTRIBUTE_CLASS; +undef $XML_ATTRIBUTE_CLASS; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use Class::Std::Fast::Storable constructor => 'none'; +use base qw(SOAP::WSDL::XSD::Typelib::ComplexType); + +Class::Std::initialize(); + +{ # BLOCK to scope variables + + +__PACKAGE__->_factory( + [ qw( + ) ], + { + }, + { + }, + { + + } +); + +} # end BLOCK + + + + + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::ReferenceParametersType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +ReferenceParametersType from the namespace http://www.w3.org/2005/08/addressing. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::ReferenceParametersType + }, + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/RelatesToType.pm b/onvif/proxy/lib/WSDiscovery11/Types/RelatesToType.pm new file mode 100644 index 000000000..068d00ce6 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/RelatesToType.pm @@ -0,0 +1,118 @@ +package WSDiscovery::Types::RelatesToType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing' }; + +our $XML_ATTRIBUTE_CLASS = 'WSDiscovery::Types::RelatesToType::_RelatesToType::XmlAttr'; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use base qw( + SOAP::WSDL::XSD::Typelib::ComplexType + SOAP::WSDL::XSD::Typelib::Builtin::anyURI +); + +package WSDiscovery::Types::RelatesToType::_RelatesToType::XmlAttr; +use base qw(SOAP::WSDL::XSD::Typelib::AttributeSet); + +{ # BLOCK to scope variables + +my %RelationshipType_of :ATTR(:get); + +__PACKAGE__->_factory( + [ qw( + RelationshipType + ) ], + { + + RelationshipType => \%RelationshipType_of, + }, + { + RelationshipType => 'WSDiscovery::Types::RelationshipTypeOpenEnum', + } +); + +} # end BLOCK + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::RelatesToType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +RelatesToType from the namespace http://www.w3.org/2005/08/addressing. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { value => $some_value }, + + + +=head2 attr + +NOTE: Attribute documentation is experimental, and may be inaccurate. +See the correspondent WSDL/XML Schema if in question. + +This class has additional attributes, accessibly via the C method. + +attr() returns an object of the class WSDiscovery::Types::RelatesToType::_RelatesToType::XmlAttr. + +The following attributes can be accessed on this object via the corresponding +get_/set_ methods: + +=over + +=item * RelationshipType + + + +This attribute is of type L. + + +=back + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/RelationshipType.pm b/onvif/proxy/lib/WSDiscovery11/Types/RelationshipType.pm new file mode 100644 index 000000000..022f9b27e --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/RelationshipType.pm @@ -0,0 +1,65 @@ +package WSDiscovery::Types::RelationshipType; +use strict; +use warnings; + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing'}; + +# derivation by restriction +use base qw( + SOAP::WSDL::XSD::Typelib::Builtin::anyURI); + + + +1; + +__END__ + +=pod + +=head1 NAME + + + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined simpleType +RelationshipType from the namespace http://www.w3.org/2005/08/addressing. + + + + + +This clase is derived from + SOAP::WSDL::XSD::Typelib::Builtin::anyURI +. SOAP::WSDL's schema implementation does not validate data, so you can use it exactly +like it's base type. + +# Description of restrictions not implemented yet. + + +=head1 METHODS + +=head2 new + +Constructor. + +=head2 get_value / set_value + +Getter and setter for the simpleType's value. + +=head1 OVERLOADING + +Depending on the simple type's base type, the following operations are overloaded + + Stringification + Numerification + Boolification + +Check L for more information. + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/RelationshipTypeOpenEnum.pm b/onvif/proxy/lib/WSDiscovery11/Types/RelationshipTypeOpenEnum.pm new file mode 100644 index 000000000..31c9df482 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/RelationshipTypeOpenEnum.pm @@ -0,0 +1,76 @@ +package WSDiscovery::Types::RelationshipTypeOpenEnum; +use strict; +use warnings; + +sub get_xmlns { 'http://www.w3.org/2005/08/addressing'}; + +# derivation by union +# union is not fully supported yet - value space constraints are not +# checked yet. +# This implementation of union resorts to the simplest possible base, which +# is: "If the or alternative is chosen, then the +# simple ur-type definition·." +# + +use base qw( + SOAP::WSDL::XSD::Typelib::Builtin::anySimpleType +); + + + +1; + +__END__ + +=pod + +=head1 NAME + + + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined simpleType +RelationshipTypeOpenEnum from the namespace http://www.w3.org/2005/08/addressing. + + + + + +This type class is derived by union. + +Derivation by union is not fully supported yet - value space constraints are +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." + + + +=head1 METHODS + +=head2 new + +Constructor. + +=head2 get_value / set_value + +Getter and setter for the simpleType's value. + +=head1 OVERLOADING + +Depending on the simple type's base type, the following operations are overloaded + + Stringification + Numerification + Boolification + +Check L for more information. + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/ResolveMatchType.pm b/onvif/proxy/lib/WSDiscovery11/Types/ResolveMatchType.pm new file mode 100644 index 000000000..03216f066 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/ResolveMatchType.pm @@ -0,0 +1,180 @@ +package WSDiscovery::Types::ResolveMatchType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' }; + +our $XML_ATTRIBUTE_CLASS; +undef $XML_ATTRIBUTE_CLASS; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use Class::Std::Fast::Storable constructor => 'none'; +use base qw(SOAP::WSDL::XSD::Typelib::ComplexType); + +Class::Std::initialize(); + +{ # BLOCK to scope variables + +my %EndpointReference_of :ATTR(:get); +my %Types_of :ATTR(:get); +my %Scopes_of :ATTR(:get); +my %XAddrs_of :ATTR(:get); +my %MetadataVersion_of :ATTR(:get); + +__PACKAGE__->_factory( + [ qw( EndpointReference + Types + Scopes + XAddrs + MetadataVersion + + ) ], + { + 'EndpointReference' => \%EndpointReference_of, + 'Types' => \%Types_of, + 'Scopes' => \%Scopes_of, + 'XAddrs' => \%XAddrs_of, + 'MetadataVersion' => \%MetadataVersion_of, + }, + { + 'EndpointReference' => 'WSDiscovery::Elements::EndpointReference', + + 'Types' => 'WSDiscovery::Elements::Types', + + 'Scopes' => 'WSDiscovery::Elements::Scopes', + + 'XAddrs' => 'WSDiscovery::Elements::XAddrs', + + 'MetadataVersion' => 'WSDiscovery::Elements::MetadataVersion', + + }, + { + + 'EndpointReference' => '', + 'Types' => '', + 'Scopes' => '', + 'XAddrs' => '', + 'MetadataVersion' => '', + } +); + +} # end BLOCK + + + + + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::ResolveMatchType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +ResolveMatchType from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + +=item * EndpointReference + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * Types + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * Scopes + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * XAddrs + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + +=item * MetadataVersion + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::ResolveMatchType + EndpointReference => { # WSDiscovery::Types::EndpointReferenceType + Address => { value => $some_value }, + ReferenceParameters => { # WSDiscovery::Types::ReferenceParametersType + }, + Metadata => { # WSDiscovery::Types::MetadataType + }, + }, + Types => $some_value, # QNameListType + Scopes => { value => $some_value }, + XAddrs => $some_value, # UriListType + MetadataVersion => $some_value, # unsignedInt + }, + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/ResolveMatchesType.pm b/onvif/proxy/lib/WSDiscovery11/Types/ResolveMatchesType.pm new file mode 100644 index 000000000..3e0db0619 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/ResolveMatchesType.pm @@ -0,0 +1,115 @@ +package WSDiscovery::Types::ResolveMatchesType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' }; + +our $XML_ATTRIBUTE_CLASS; +undef $XML_ATTRIBUTE_CLASS; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use Class::Std::Fast::Storable constructor => 'none'; +use base qw(SOAP::WSDL::XSD::Typelib::ComplexType); + +Class::Std::initialize(); + +{ # BLOCK to scope variables + +my %ResolveMatch_of :ATTR(:get); + +__PACKAGE__->_factory( + [ qw( ResolveMatch + + ) ], + { + 'ResolveMatch' => \%ResolveMatch_of, + }, + { + 'ResolveMatch' => 'WSDiscovery::Types::ResolveMatchType', + }, + { + + 'ResolveMatch' => 'ResolveMatch', + } +); + +} # end BLOCK + + + + + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::ResolveMatchesType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +ResolveMatchesType from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + +=item * ResolveMatch + + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::ResolveMatchesType + ResolveMatch => { # WSDiscovery::Types::ResolveMatchType + EndpointReference => { # WSDiscovery::Types::EndpointReferenceType + Address => { value => $some_value }, + ReferenceParameters => { # WSDiscovery::Types::ReferenceParametersType + }, + Metadata => { # WSDiscovery::Types::MetadataType + }, + }, + Types => $some_value, # QNameListType + Scopes => { value => $some_value }, + XAddrs => $some_value, # UriListType + MetadataVersion => $some_value, # unsignedInt + }, + }, + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/ResolveType.pm b/onvif/proxy/lib/WSDiscovery11/Types/ResolveType.pm new file mode 100644 index 000000000..f1ddd13f5 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/ResolveType.pm @@ -0,0 +1,116 @@ +package WSDiscovery::Types::ResolveType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' }; + +our $XML_ATTRIBUTE_CLASS; +undef $XML_ATTRIBUTE_CLASS; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use Class::Std::Fast::Storable constructor => 'none'; +use base qw(SOAP::WSDL::XSD::Typelib::ComplexType); + +Class::Std::initialize(); + +{ # BLOCK to scope variables + +my %EndpointReference_of :ATTR(:get); + +__PACKAGE__->_factory( + [ qw( EndpointReference + + ) ], + { + 'EndpointReference' => \%EndpointReference_of, + }, + { + 'EndpointReference' => 'WSDiscovery::Elements::EndpointReference', + + }, + { + + 'EndpointReference' => '', + } +); + +} # end BLOCK + + + + + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::ResolveType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +ResolveType from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + +=item * EndpointReference + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::ResolveType + EndpointReference => { # WSDiscovery::Types::EndpointReferenceType + Address => { value => $some_value }, + ReferenceParameters => { # WSDiscovery::Types::ReferenceParametersType + }, + Metadata => { # WSDiscovery::Types::MetadataType + }, + }, + }, + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/ScopesType.pm b/onvif/proxy/lib/WSDiscovery11/Types/ScopesType.pm new file mode 100644 index 000000000..84cdfa057 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/ScopesType.pm @@ -0,0 +1,118 @@ +package WSDiscovery::Types::ScopesType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' }; + +our $XML_ATTRIBUTE_CLASS = 'WSDiscovery::Types::ScopesType::_ScopesType::XmlAttr'; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use base qw( + SOAP::WSDL::XSD::Typelib::ComplexType + WSDiscovery::Types::UriListType +); + +package WSDiscovery::Types::ScopesType::_ScopesType::XmlAttr; +use base qw(SOAP::WSDL::XSD::Typelib::AttributeSet); + +{ # BLOCK to scope variables + +my %MatchBy_of :ATTR(:get); + +__PACKAGE__->_factory( + [ qw( + MatchBy + ) ], + { + + MatchBy => \%MatchBy_of, + }, + { + MatchBy => 'SOAP::WSDL::XSD::Typelib::Builtin::anyURI', + } +); + +} # end BLOCK + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::ScopesType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +ScopesType from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { value => $some_value }, + + + +=head2 attr + +NOTE: Attribute documentation is experimental, and may be inaccurate. +See the correspondent WSDL/XML Schema if in question. + +This class has additional attributes, accessibly via the C method. + +attr() returns an object of the class WSDiscovery::Types::ScopesType::_ScopesType::XmlAttr. + +The following attributes can be accessed on this object via the corresponding +get_/set_ methods: + +=over + +=item * MatchBy + + + +This attribute is of type L. + + +=back + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/SecurityType.pm b/onvif/proxy/lib/WSDiscovery11/Types/SecurityType.pm new file mode 100644 index 000000000..968033307 --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/SecurityType.pm @@ -0,0 +1,111 @@ +package WSDiscovery::Types::SecurityType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' }; + +our $XML_ATTRIBUTE_CLASS; +undef $XML_ATTRIBUTE_CLASS; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use Class::Std::Fast::Storable constructor => 'none'; +use base qw(SOAP::WSDL::XSD::Typelib::ComplexType); + +Class::Std::initialize(); + +{ # BLOCK to scope variables + +my %Sig_of :ATTR(:get); + +__PACKAGE__->_factory( + [ qw( Sig + + ) ], + { + 'Sig' => \%Sig_of, + }, + { + 'Sig' => 'WSDiscovery::Elements::Sig', + + }, + { + + 'Sig' => '', + } +); + +} # end BLOCK + + + + + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::SecurityType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +SecurityType from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + +=item * Sig + +Note: The name of this property has been altered, because it didn't match +perl's notion of variable/subroutine names. The altered name is used in +perl code only, XML output uses the original name: + + + + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::SecurityType + Sig => { # WSDiscovery::Types::SigType + }, + }, + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/SigType.pm b/onvif/proxy/lib/WSDiscovery11/Types/SigType.pm new file mode 100644 index 000000000..71eb613df --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/SigType.pm @@ -0,0 +1,172 @@ +package WSDiscovery::Types::SigType; +use strict; +use warnings; + + +__PACKAGE__->_set_element_form_qualified(0); + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01' }; + +our $XML_ATTRIBUTE_CLASS = 'WSDiscovery::Types::SigType::_SigType::XmlAttr'; + +sub __get_attr_class { + return $XML_ATTRIBUTE_CLASS; +} + +use Class::Std::Fast::Storable constructor => 'none'; +use base qw(SOAP::WSDL::XSD::Typelib::ComplexType); + +Class::Std::initialize(); + +{ # BLOCK to scope variables + + +__PACKAGE__->_factory( + [ qw( + ) ], + { + }, + { + }, + { + + } +); + +} # end BLOCK + + + + +package WSDiscovery::Types::SigType::_SigType::XmlAttr; +use base qw(SOAP::WSDL::XSD::Typelib::AttributeSet); + +{ # BLOCK to scope variables + +my %Scheme_of :ATTR(:get); +my %KeyId_of :ATTR(:get); +my %Refs_of :ATTR(:get); +my %Sig_of :ATTR(:get); + +__PACKAGE__->_factory( + [ qw( + Scheme + KeyId + Refs + Sig + ) ], + { + + Scheme => \%Scheme_of, + + KeyId => \%KeyId_of, + + Refs => \%Refs_of, + + Sig => \%Sig_of, + }, + { + Scheme => 'SOAP::WSDL::XSD::Typelib::Builtin::anyURI', + KeyId => 'SOAP::WSDL::XSD::Typelib::Builtin::base64Binary', + Refs => 'SOAP::WSDL::XSD::Typelib::Builtin::IDREFS', + Sig => 'SOAP::WSDL::XSD::Typelib::Builtin::base64Binary', + } +); + +} # end BLOCK + + + + +1; + + +=pod + +=head1 NAME + +WSDiscovery::Types::SigType + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined complexType +SigType from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + + +=head2 PROPERTIES + +The following properties may be accessed using get_PROPERTY / set_PROPERTY +methods: + +=over + + + +=back + + +=head1 METHODS + +=head2 new + +Constructor. The following data structure may be passed to new(): + + { # WSDiscovery::Types::SigType + }, + + + +=head2 attr + +NOTE: Attribute documentation is experimental, and may be inaccurate. +See the correspondent WSDL/XML Schema if in question. + +This class has additional attributes, accessibly via the C method. + +attr() returns an object of the class WSDiscovery::Types::SigType::_SigType::XmlAttr. + +The following attributes can be accessed on this object via the corresponding +get_/set_ methods: + +=over + +=item * Scheme + + + +This attribute is of type L. + +=item * KeyId + + + +This attribute is of type L. + +=item * Refs + + + +This attribute is of type L. + +=item * Sig + + + +This attribute is of type L. + + +=back + + + + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSDiscovery11/Types/UriListType.pm b/onvif/proxy/lib/WSDiscovery11/Types/UriListType.pm new file mode 100644 index 000000000..1a04cfbee --- /dev/null +++ b/onvif/proxy/lib/WSDiscovery11/Types/UriListType.pm @@ -0,0 +1,75 @@ +package WSDiscovery::Types::UriListType; +use strict; +use warnings; + +sub get_xmlns { 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01'}; + + +# list derivation +use base qw( + SOAP::WSDL::XSD::Typelib::Builtin::list + SOAP::WSDL::XSD::Typelib::Builtin::anyURI +); + + + + + +1; + +__END__ + +=pod + +=head1 NAME + + + +=head1 DESCRIPTION + +Perl data type class for the XML Schema defined simpleType +UriListType from the namespace http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01. + + + + + +This clase is derived from + SOAP::WSDL::XSD::Typelib::Builtin::anyURI +. + +You may pass the following structure to new(): + + [ $value_1, .. $value_n ] + +All elements of the list must be of the class' base type (or valid arguments +to it's constructor). + + + +=head1 METHODS + +=head2 new + +Constructor. + +=head2 get_value / set_value + +Getter and setter for the simpleType's value. + +=head1 OVERLOADING + +Depending on the simple type's base type, the following operations are overloaded + + Stringification + Numerification + Boolification + +Check L for more information. + +=head1 AUTHOR + +Generated by SOAP::WSDL + +=cut + diff --git a/onvif/proxy/lib/WSNotification/Interfaces/WSBaseNotificationSender/NotificationProducerPort.pm b/onvif/proxy/lib/WSNotification/Interfaces/WSBaseNotificationSender/NotificationProducerPort.pm index ee55035ca..5a8f4d0b1 100644 --- a/onvif/proxy/lib/WSNotification/Interfaces/WSBaseNotificationSender/NotificationProducerPort.pm +++ b/onvif/proxy/lib/WSNotification/Interfaces/WSBaseNotificationSender/NotificationProducerPort.pm @@ -20,18 +20,16 @@ sub START { sub Subscribe { my ($self, $body, $header) = @_; die "Subscribe must be called as object method (\$self is <$self>)" if not blessed($self); +#print " proxy/lib/WSNotification/Interfaces/WSBaseNotificationSender/NotificationProducerPort.pm Subscribe\n"; return $self->SUPER::call({ operation => 'Subscribe', soap_action => 'http://docs.oasis-open.org/wsn/bw-2/Subscribe', style => 'document', body => { - - - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( WSNotification::Elements::Subscribe )], - + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( WSNotification::Elements::Subscribe )], }, header => { @@ -42,39 +40,30 @@ sub Subscribe { }, $body, $header); } - sub GetCurrentMessage { - my ($self, $body, $header) = @_; - die "GetCurrentMessage must be called as object method (\$self is <$self>)" if not blessed($self); - return $self->SUPER::call({ - operation => 'GetCurrentMessage', - soap_action => 'http://docs.oasis-open.org/wsn/bw-2/GetCurrentMessage', - style => 'document', - body => { - + my ($self, $body, $header) = @_; + die "GetCurrentMessage must be called as object method (\$self is <$self>)" if not blessed($self); + return $self->SUPER::call({ + operation => 'GetCurrentMessage', + soap_action => 'http://docs.oasis-open.org/wsn/bw-2/GetCurrentMessage', + style => 'document', + body => { + use => 'literal', + namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', + encodingStyle => '', + parts => [qw( WSNotification::Elements::GetCurrentMessage )], - 'use' => 'literal', - namespace => 'http://schemas.xmlsoap.org/wsdl/soap/', - encodingStyle => '', - parts => [qw( WSNotification::Elements::GetCurrentMessage )], + }, + header => { - }, - header => { - - }, - headerfault => { - - } - }, $body, $header); + }, + headerfault => { + + } + }, $body, $header); } - - - 1; - - - __END__ =pod 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/onvif/wsdl/analytics.wsdl b/onvif/wsdl/analytics.wsdl new file mode 100644 index 000000000..b2c0f6ae7 --- /dev/null +++ b/onvif/wsdl/analytics.wsdl @@ -0,0 +1,532 @@ + + + + + + + + + + + + + + + + + + + + The capabilities for the analytics service is returned in the Capabilities element. + + + + + + + + + + + + + Indication that the device supports the rules interface and the rules syntax. + + + + + Indication that the device supports the scene analytics module interface. + + + + + Indication that the device produces the cell based scene description + + + + + + + + + + + + + References an existing Video Analytics configuration. The list of available tokens can be obtained + via the Media service GetVideoAnalyticsConfigurations method. + + + + + + + + + + + + + + + + + + + + Reference to an existing VideoAnalyticsConfiguration. + + + + + + + + + + + + + + + + Reference to an existing VideoAnalyticsConfiguration. + + + + + References the specific rule to be deleted (e.g. "MyLineDetector"). + + + + + + + + + + + + + + + Reference to an existing VideoAnalyticsConfiguration. + + + + + + + + + + + + + + + + Reference to an existing VideoAnalyticsConfiguration. + + + + + + + + + + + + + + + + + + + Reference to an existing VideoAnalyticsConfiguration. + + + + + + + + + + + + + + + + + + + Reference to an existing VideoAnalyticsConfiguration. + + + + + + + + + + + + + + + + Reference to an existing Video Analytics configuration. + + + + + Name of the AnalyticsModule to be deleted. + + + + + + + + + + + + + + + Reference to an existing VideoAnalyticsConfiguration. + + + + + + + + + + + + + + + + Reference to an existing VideoAnalyticsConfiguration. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + List all rules that are supported by the given VideoAnalyticsConfiguration. + The result of this method may depend on the overall Video analytics configuration of the device, + which is available via the current set of profiles. + + + + + + + Add one or more rules to an existing VideoAnalyticsConfiguration. + The available supported types can be retrieved via GetSupportedRules, + where the Name of the supported rule correspond to the type of an rule instance.
+ Pass unique module names which can be later used as reference. + The Parameters of the rules must match those of the corresponding description. +
+ Although this method is mandatory a device implementation must not support adding rules. + Instead it can provide a fixed set of predefined configurations via the media service function + GetCompatibleVideoAnalyticsConfigurations. +
+ + +
+ + + Remove one or more rules from a VideoAnalyticsConfiguration. + + + + + + + List the currently assigned set of rules of a VideoAnalyticsConfiguration. + + + + + + + Modify one or more rules of a VideoAnalyticsConfiguration. The rules are referenced by their names. + + + + +
+ + + Returns the capabilities of the analytics service. The result is returned in a typed answer. + + + + + + List all analytics modules that are supported by the given VideoAnalyticsConfiguration. + The result of this method may depend on the overall Video analytics configuration of the device, + which is available via the current set of profiles. + + + + + + + Add one or more analytics modules to an existing VideoAnalyticsConfiguration. + The available supported types can be retrieved via GetSupportedAnalyticsModules, + where the Name of the supported AnalyticsModules correspond to the type of an AnalyticsModule instance.
+ Pass unique module names which can be later used as reference. The Parameters of the analytics module must match those of the corresponding AnalyticsModuleDescription. +
+ Although this method is mandatory a device implementation must not support adding modules. + Instead it can provide a fixed set of predefined configurations via the media service function + GetCompatibleVideoAnalyticsConfigurations. +
+ The device shall ensure that a corresponding analytics engine starts operation when a client + subscribes directly or indirectly for events produced by the analytics or rule engine or when a + client requests the corresponding scene description stream. + An analytics module must be attached to a Video source using the media profiles before it can be used. + In case differing analytics configurations are attached to the same profile it is undefined which + of the analytics module configuration becomes active if no stream is activated or multiple streams + with different profiles are activated at the same time. +
+ + +
+ + + Remove one or more analytics modules from a VideoAnalyticsConfiguration referenced by their names.
+
+ + +
+ + + List the currently assigned set of analytics modules of a VideoAnalyticsConfiguration. + + + + + + + Modify the settings of one or more analytics modules of a VideoAnalyticsConfiguration. The modules are referenced by their names. + It is allowed to pass only a subset to be modified. + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/onvif/wsdl/b-2.xsd b/onvif/wsdl/b-2.xsd new file mode 100644 index 000000000..6af08aee9 --- /dev/null +++ b/onvif/wsdl/b-2.xsd @@ -0,0 +1,586 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onvif/wsdl/bf-2.xsd b/onvif/wsdl/bf-2.xsd new file mode 100644 index 000000000..4efefea72 --- /dev/null +++ b/onvif/wsdl/bf-2.xsd @@ -0,0 +1,86 @@ + + + + + + + + + + Get access to the xml: attribute groups for xml:lang as declared on 'schema' + and 'documentation' below + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onvif/wsdl/br-2.xsd b/onvif/wsdl/br-2.xsd new file mode 100644 index 000000000..3ed96e022 --- /dev/null +++ b/onvif/wsdl/br-2.xsd @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/onvif/wsdl/brw-2.wsdl b/onvif/wsdl/brw-2.wsdl new file mode 100644 index 000000000..675bab2df --- /dev/null +++ b/onvif/wsdl/brw-2.wsdl @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/onvif/wsdl/bw-2.wsdl b/onvif/wsdl/bw-2.wsdl new file mode 100644 index 000000000..fad3f2957 --- /dev/null +++ b/onvif/wsdl/bw-2.wsdl @@ -0,0 +1,450 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onvif/wsdl/devicemgmt.wsdl b/onvif/wsdl/devicemgmt.wsdl new file mode 100644 index 000000000..d7f7379c1 --- /dev/null +++ b/onvif/wsdl/devicemgmt.wsdl @@ -0,0 +1,3786 @@ + + + + + + + + + + + + + + Indicates if the service capabilities (untyped) should be included in the response. + + + + + + + + + + + Each Service element contains information about one service. + + + + + + + + + + + Namespace of the service being described. This parameter allows to match the service capabilities to the service. Note that only one set of capabilities is supported per namespace. + + + + + The transport addresses where the service can be reached. The scheme and IP part shall match the one used in the request (i.e. the GetServices request). + + + + + + + + The placeholder for the service capabilities. The service capability element shall be returned here. For example for the device service that would be the tds:DeviceServiceCapabilities element (not complextype). + + + + + + + + The version of the service (not the ONVIF core spec version). + + + + + + + + + + + + + + + + + + The capabilities for the device service is returned in the Capabilities element. + + + + + + + + + + + Network capabilities. + + + + + Security capabilities. + + + + + System capabilities. + + + + + Capabilities that do not fit in any of the other categories. + + + + + + + + + + Indicates support for IP filtering. + + + + + Indicates support for zeroconf. + + + + + Indicates support for IPv6. + + + + + Indicates support for dynamic DNS configuration. + + + + + Indicates support for IEEE 802.11 configuration. + + + + + Indicates the maximum number of Dot1X configurations supported by the device + + + + + Indicates support for retrieval of hostname from DHCP. + + + + + Maximum number of NTP servers supported by the devices SetNTP command. + + + + + Indicates support for Stateful IPv6 DHCP. + + + + + + + + + + + + Indicates support for TLS 1.0. + + + + + Indicates support for TLS 1.1. + + + + + Indicates support for TLS 1.2. + + + + + Indicates support for onboard key generation. + + + + + Indicates support for access policy configuration. + + + + + Indicates support for the ONVIF default access policy. + + + + + Indicates support for IEEE 802.1X configuration. + + + + + Indicates support for remote user configuration. Used when accessing another device. + + + + + Indicates support for WS-Security X.509 token. + + + + + Indicates support for WS-Security SAML token. + + + + + Indicates support for WS-Security Kerberos token. + + + + + Indicates support for WS-Security Username token. + + + + + Indicates support for WS over HTTP digest authenticated communication layer. + + + + + Indicates support for WS-Security REL token. + + + + + EAP Methods supported by the device. The int values refer to the IANA EAP Registry. + + + + + The maximum number of users that the device supports. + + + + + + + + + Indicates support for WS Discovery resolve requests. + + + + + Indicates support for WS-Discovery Bye. + + + + + Indicates support for remote discovery. + + + + + Indicates support for system backup through MTOM. + + + + + Indicates support for retrieval of system logging through MTOM. + + + + + Indicates support for firmware upgrade through MTOM. + + + + + Indicates support for system backup through MTOM. + + + + + Indicates support for system backup through HTTP. + + + + + Indicates support for retrieval of system logging through HTTP. + + + + + Indicates support for retrieving support information through HTTP. + + + + + + + + + Lists of commands supported by SendAuxiliaryCommand. + + + + + + + + + + + + + + + + The manufactor of the device. + + + + + The device model. + + + + + The firmware version in the device. + + + + + The serial number of the device. + + + + + The hardware ID of the device. + + + + + + + + + + + + Defines if the date and time is set via NTP or manually. + + + + + Automatically adjust Daylight savings if defined in TimeZone. + + + + + The time zone in POSIX 1003.1 format + + + + + Date and time in UTC. If time is obtained via NTP, UTCDateTime has no meaning + + + + + + + + + + + + + + + + + + + + + + + Contains information whether system date and time are set manually or by NTP, daylight savings is on or off, time zone in POSIX 1003.1 format and system date and time in UTC and also local system date and time. + + + + + + + + + + + + Specifies the factory default action type. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Contains the reboot message sent by the device. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Contains the arbitary device diagnostics information. + + + + + + + + + + + + Specifies the type of system log to get. + + + + + + + + + + + Contains the system log information. + + + + + + + + + + + + + + + + + + Contains a list of URI definining the device scopes. Scope parameters can be of two types: fixed and configurable. Fixed parameters can not be altered. + + + + + + + + + + + + Contains a list of scope parameters that will replace all existing configurable scope parameters. + + + + + + + + + + + + + + + + + + Contains a list of new configurable scope parameters that will be added to the existing configurable scope. + + + + + + + + + + + + + + + + + + Contains a list of URIs that should be removed from the device scope.
+ Note that the response message always will match the request or an error will be returned. The use of the response is for that reason deprecated. +
+
+
+
+
+
+ + + + + + Contains a list of URIs that has been removed from the device scope + + + + + + + + + + + + + + + + + + + Indicator of discovery mode: Discoverable, NonDiscoverable. + + + + + + + + + + + + + + Indicator of discovery mode: Discoverable, NonDiscoverable. + + + + + + + + + + + + + + + + + + + + + + + + + + Indicator of discovery mode: Discoverable, NonDiscoverable. + + + + + + + + + + + + + + Indicator of discovery mode: Discoverable, NonDiscoverable. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Contains a list of the onvif users and following information is included in each entry: username and user level. + + + + + + + + + + + + Creates new device users and corresponding credentials. Each user entry includes: username, password and user level. Either all users are created successfully or a fault message MUST be returned without creating any user. If trying to create several users with exactly the same username the request is rejected and no users are created. If password is missing, then fault message Too weak password is returned. + + + + + + + + + + + + + + + + + + Deletes users on an device and there may exist users that cannot be deleted to ensure access to the unit. Either all users are deleted successfully or a fault message MUST be returned and no users be deleted. If a username exists multiple times in the request, then a fault message is returned. + + + + + + + + + + + + + + + + + + Updates the credentials for one or several users on an device. Either all change requests are processed successfully or a fault message MUST be returned. If the request contains the same username multiple times, a fault message is returned. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + List of categories to retrieve capability information on. + + + + + + + + + + + + + Capability information. + + + + + + + + + + + + + + + + + + + Contains the hostname information. + + + + + + + + + + + + The hostname to set. + + + + + + + + + + + + + + + + + + True if the hostname shall be obtained via DHCP. + + + + + + + + + + + + Indicates whether or not a reboot is required after configuration updates. + + + + + + + + + + + + + + + + + + + + DNS information. + + + + + + + + + + + + + + Indicate if the DNS address is to be retrieved using DHCP. + + + + + + + DNS search domain. + + + + + + + DNS address(es) set manually. + + + + + + + + + + + + + + + + + + + + + + + + + + NTP information. + + + + + + + + + + + + + + Indicate if NTP address information is to be retrieved using DHCP. + + + + + + + Manual NTP settings. + + + + + + + + + + + + + + + + + + + + + + + + + + Dynamic DNS information. + + + + + + + + + + + + + + Dynamic DNS type. + + + + + + + DNS name. + + + + + + + DNS record time to live. + + + + + + + + + + + + + + + + + + + + + + + + + + List of network interfaces. + + + + + + + + + + + + + + Symbolic network interface name. + + + + + + + Network interface name. + + + + + + + + + + + + + Indicates whether or not a reboot is required after configuration updates. + If a device responds with RebootNeeded set to false, the device can be reached + via the new IP address without further action. A client should be aware that a device + may not be responsive for a short period of time until it signals availability at + the new address via the discovery Hello messages. + If a device responds with RebootNeeded set to true, it will be further available under + its previous IP address. The settings will only be activated when the device is + rebooted via the SystemReboot command. + + + + + + + + + + + + + + + + + + + Contains an array of defined protocols supported by the device. There are three protocols defined; HTTP, HTTPS and RTSP. The following parameters can be retrieved for each protocol: port and enable/disable. + + + + + + + + + + + + Configures one or more defined network protocols supported by the device. There are currently three protocols defined; HTTP, HTTPS and RTSP. The following parameters can be set for each protocol: port and enable/disable. + + + + + + + + + + + + + + + + + + + + + + + + Gets the default IPv4 and IPv6 gateway settings from the device. + + + + + + + + + + + + Sets IPv4 gateway address used as default setting. + + + + + Sets IPv6 gateway address used as default setting. + + + + + + + + + + + + + + + + + + + + + + + + Contains the zero-configuration. + + + + + + + + + + + + Unique identifier referencing the physical interface. + + + + + Specifies if the zero-configuration should be enabled or not. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Certificate id. + + + + + Identification of the entity associated with the public-key. + + + + + Certificate validity start date. + + + + + Certificate expiry start date. + + + + + + + + + + + + base64 encoded DER representation of certificate. + + + + + + + + + + + + + + + + + + + + Id and base64 encoded DER representation of all available certificates. + + + + + + + + + + + + + + + + + + + + Indicates if a certificate is used in an optional HTTPS configuration of the device. + + + + + + + + + + + + + + Indicates if a certificate is to be used in an optional HTTPS configuration of the device. + + + + + + + + + + + + + + + + + + + + List of ids of certificates to delete. + + + + + + + + + + + + + + + + + + + + List of ids of certificates to delete. + + + + + + + Relative Dinstinguished Name(RDN) CommonName(CN). + + + + + + + Optional base64 encoded DER attributes. + + + + + + + + + + + + + base64 encoded DER representation of certificate. + + + + + + + + + + + + + + Optional id and base64 encoded DER representation of certificate. + + + + + + + + + + + + + + + + + + + + + + + + + + Indicates whether or not client certificates are required by device. + + + + + + + + + + + + + + Indicates whether or not client certificates are required by device. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Returns information about services on the device. + + + + + Returns the capabilities of the device service. The result is returned in a typed answer. + + + + + This operation gets basic device information from the device. + + + + + This operation sets the device system date and time. The device shall support the + configuration of the daylight saving setting and of the manual system date and time (if + applicable) or indication of NTP time (if applicable) through the SetSystemDateAndTime + command.
+ If system time and date are set manually, the client shall include UTCDateTime in the request.
+ A TimeZone token which is not formed according to the rules of IEEE 1003.1 section 8.3 is considered as invalid timezone.
+ The DayLightSavings flag should be set to true to activate any DST settings of the TimeZone string. + Clear the DayLightSavings flag if the DST portion of the TimeZone settings should be ignored. +
+ + +
+ + This operation gets the device system date and time. The device shall support the return of + the daylight saving setting and of the manual system date and time (if applicable) or indication + of NTP time (if applicable) through the GetSystemDateAndTime command.
+ A device shall provide the UTCDateTime information.
+ + +
+ + This operation reloads the parameters on the device to their factory default values. + + + + + This operation upgrades a device firmware version. After a successful upgrade the response + message is sent before the device reboots. The device should support firmware upgrade + through the UpgradeSystemFirmware command. The exact format of the firmware data is + outside the scope of this standard. + + + + + This operation reboots the device. + + + + + This operation restores the system backup configuration files(s) previously retrieved from a + device. The device should support restore of backup configuration file(s) through the + RestoreSystem command. The exact format of the backup configuration file(s) is outside the + scope of this standard. If the command is supported, it shall accept backup files returned by + the GetSystemBackup command. + + + + + This operation is retrieves system backup configuration file(s) from a device. The device + should support return of back up configuration file(s) through the GetSystemBackup command. + The backup is returned with reference to a name and mime-type together with binary data. + The exact format of the backup configuration files is outside the scope of this standard. + + + + + This operation gets a system log from the device. The exact format of the system logs is outside the scope of this standard. + + + + + This operation gets arbitary device diagnostics information from the device. + + + + + This operation requests the scope parameters of a device. The scope parameters are used in + the device discovery to match a probe message, see Section 7. The Scope parameters are of + two different types:
    +
  • Fixed
  • +
  • Configurable
  • +
+ Fixed scope parameters are permanent device characteristics and cannot be removed through the device management interface. + The scope type is indicated in the scope list returned in the get scope parameters response. A device shall support + retrieval of discovery scope parameters through the GetScopes command. As some scope parameters are mandatory, + the device shall return a non-empty scope list in the response.
+ + +
+ + This operation sets the scope parameters of a device. The scope parameters are used in the + device discovery to match a probe message. + This operation replaces all existing configurable scope parameters (not fixed parameters). If + this shall be avoided, one should use the scope add command instead. The device shall + support configuration of discovery scope parameters through the SetScopes command. + + + + + This operation adds new configurable scope parameters to a device. The scope parameters + are used in the device discovery to match a probe message. The device shall + support addition of discovery scope parameters through the AddScopes command. + + + + + This operation deletes scope-configurable scope parameters from a device. The scope + parameters are used in the device discovery to match a probe message, see Section 7. The + device shall support deletion of discovery scope parameters through the RemoveScopes + command. + Table + + + + + This operation gets the discovery mode of a device. See Section 7.2 for the definition of the + different device discovery modes. The device shall support retrieval of the discovery mode + setting through the GetDiscoveryMode command. + + + + + This operation sets the discovery mode operation of a device. See Section 7.2 for the + definition of the different device discovery modes. The device shall support configuration of + the discovery mode setting through the SetDiscoveryMode command. + + + + + This operation gets the remote discovery mode of a device. See Section 7.4 for the definition + of remote discovery extensions. A device that supports remote discovery shall support + retrieval of the remote discovery mode setting through the GetRemoteDiscoveryMode + command. + + + + + This operation sets the remote discovery mode of operation of a device. See Section 7.4 for + the definition of remote discovery remote extensions. A device that supports remote discovery + shall support configuration of the discovery mode setting through the + SetRemoteDiscoveryMode command. + + + + + This operation gets the remote DP address or addresses from a device. If the device supports + remote discovery, as specified in Section 7.4, the device shall support retrieval of the remote + DP address(es) through the GetDPAddresses command. + + + + + This operation sets the remote DP address or addresses on a device. If the device supports + remote discovery, as specified in Section 7.4, the device shall support configuration of the + remote DP address(es) through the SetDPAddresses command. + + + + + A client can ask for the device service endpoint reference address property that can be used + to derive the password equivalent for remote user operation. The device shall support the + GetEndpointReference command returning the address property of the device service + endpoint reference. + + + + + This operation returns the configured remote user (if any). A device supporting remote user + handling shall support this operation. The user is only valid for the WS-UserToken profile or + as a HTTP / RTSP user.
+ The algorithm to use for deriving the password is described in section 5.12.2.1 of the core specification.
+ + +
+ + This operation sets the remote user. A device supporting remote user handling shall support this + operation. The user is only valid for the WS-UserToken profile or as a HTTP / RTSP user.
+ The password that is set shall always be the original (not derived) password.
+ If UseDerivedPassword is set password derivation shall be done by the device when connecting to a + remote device.The algorithm to use for deriving the password is described in section 5.12.2.1 of the core specification.
+ To remove the remote user SetRemoteUser should be called without the RemoteUser parameter.
+ + +
+ + This operation lists the registered users and corresponding credentials on a device. The + device shall support retrieval of registered device users and their credentials for the user + token through the GetUsers command. + + + + + This operation creates new device users and corresponding credentials on a device for authentication purposes. + The device shall support creation of device users and their credentials through the CreateUsers + command. Either all users are created successfully or a fault message shall be returned + without creating any user.
+ ONVIF compliant devices are recommended to support password length of at least 28 bytes, + as clients may follow the password derivation mechanism which results in 'password + equivalent' of length 28 bytes, as described in section 3.1.2 of the ONVIF security white paper.
+ + +
+ + This operation deletes users on a device. The device shall support deletion of device users and their credentials + through the DeleteUsers command. A device may have one or more fixed users + that cannot be deleted to ensure access to the unit. Either all users are deleted successfully or a + fault message shall be returned and no users be deleted. + + + + + This operation updates the settings for one or several users on a device for authentication purposes. + The device shall support update of device users and their credentials through the SetUser command. + Either all change requests are processed successfully or a fault message shall be returned and no change requests be processed. + + + + + It is possible for an endpoint to request a URL that can be used to retrieve the complete + schema and WSDL definitions of a device. The command gives in return a URL entry point + where all the necessary product specific WSDL and schema definitions can be retrieved. The + device shall provide a URL for WSDL and schema download through the GetWsdlUrl command. + + + + + Any endpoint can ask for the capabilities of a device using the capability exchange request + response operation. The device shall indicate all its ONVIF compliant capabilities through the + GetCapabilities command. + The capability list includes references to the addresses (XAddr) of the service implementing + the interface operations in the category. Apart from the addresses, the + capabilities only reflect optional functions. + + + + + This operation is used by an endpoint to get the hostname from a device. The device shall + return its hostname configurations through the GetHostname command. + + + + + This operation sets the hostname on a device. It shall be possible to set the device hostname + configurations through the SetHostname command.
+ A device shall accept string formated according to RFC 1123 section 2.1 or alternatively to RFC 952, + other string shall be considered as invalid strings. +
+ + +
+ + This operation controls whether the hostname is set manually or retrieved via DHCP. + + + + + This operation gets the DNS settings from a device. The device shall return its DNS + configurations through the GetDNS command. + + + + + This operation sets the DNS settings on a device. It shall be possible to set the device DNS + configurations through the SetDNS command. + + + + + This operation gets the NTP settings from a device. If the device supports NTP, it shall be + possible to get the NTP server settings through the GetNTP command. + + + + + This operation sets the NTP settings on a device. If the device supports NTP, it shall be + possible to set the NTP server settings through the SetNTP command.
+ A device shall accept string formated according to RFC 1123 section 2.1 or alternatively to RFC 952, + other string shall be considered as invalid strings.
+ Changes to the NTP server list will not affect the clock mode DateTimeType. Use SetSystemDateAndTime to activate NTP operation. +
+ + +
+ + This operation gets the dynamic DNS settings from a device. If the device supports dynamic + DNS as specified in [RFC 2136] and [RFC 4702], it shall be possible to get the type, name + and TTL through the GetDynamicDNS command. + + + + + This operation sets the dynamic DNS settings on a device. If the device supports dynamic + DNS as specified in [RFC 2136] and [RFC 4702], it shall be possible to set the type, name + and TTL through the SetDynamicDNS command. + + + + + This operation gets the network interface configuration from a device. The device shall + support return of network interface configuration settings as defined by the NetworkInterface + type through the GetNetworkInterfaces command. + + + + + This operation sets the network interface configuration on a device. The device shall support + network configuration of supported network interfaces through the SetNetworkInterfaces + command.
+ For interoperability with a client unaware of the IEEE 802.11 extension a device shall retain + its IEEE 802.11 configuration if the IEEE 802.11 configuration element isn’t present in the + request.
+ + +
+ + This operation gets defined network protocols from a device. The device shall support the + GetNetworkProtocols command returning configured network protocols. + + + + + This operation configures defined network protocols on a device. The device shall support + configuration of defined network protocols through the SetNetworkProtocols command. + + + + + This operation gets the default gateway settings from a device. The device shall support the + GetNetworkDefaultGateway command returning configured default gateway address(es). + + + + + This operation sets the default gateway settings on a device. The device shall support + configuration of default gateway through the SetNetworkDefaultGateway command. + + + + + This operation gets the zero-configuration from a device. If the device supports dynamic IP + configuration according to [RFC3927], it shall support the return of IPv4 zero configuration + address and status through the GetZeroConfiguration command.
+ Devices supporting zero configuration on more than one interface shall use the extension to list the additional interface settings.
+ + +
+ + This operation sets the zero-configuration. Use GetCapalities to get if zero-zero-configuration is supported or not. + + + + + This operation gets the IP address filter settings from a device. If the device supports device + access control based on IP filtering rules (denied or accepted ranges of IP addresses), the + device shall support the GetIPAddressFilter command. + + + + + This operation sets the IP address filter settings on a device. If the device supports device + access control based on IP filtering rules (denied or accepted ranges of IP addresses), the + device shall support configuration of IP filtering rules through the SetIPAddressFilter + command. + + + + + This operation adds an IP filter address to a device. If the device supports device access + control based on IP filtering rules (denied or accepted ranges of IP addresses), the device + shall support adding of IP filtering addresses through the AddIPAddressFilter command. + + + + + This operation deletes an IP filter address from a device. If the device supports device access + control based on IP filtering rules (denied or accepted ranges of IP addresses), the device + shall support deletion of IP filtering addresses through the RemoveIPAddressFilter command. + + + + + Access to different services and sub-sets of services should be subject to access control. The + WS-Security framework gives the prerequisite for end-point authentication. Authorization + decisions can then be taken using an access security policy. This standard does not mandate + any particular policy description format or security policy but this is up to the device + manufacturer or system provider to choose policy and policy description format of choice. + However, an access policy (in arbitrary format) can be requested using this command. If the + device supports access policy settings based on WS-Security authentication, then the device + shall support this command. + + + + + This command sets the device access security policy (for more details on the access security + policy see the Get command). If the device supports access policy settings + based on WS-Security authentication, then the device shall support this command. + + + + + This operation generates a private/public key pair and also can create a self-signed device + certificate as a result of key pair generation. The certificate is created using a suitable + onboard key pair generation mechanism.
+ If a device supports onboard key pair generation, the device that supports TLS shall support + this certificate creation command. And also, if a device supports onboard key pair generation, + the device that support IEEE 802.1X shall support this command for the purpose of key pair + generation. Certificates and key pairs are identified using certificate IDs. These IDs are either + chosen by the certificate generation requester or by the device (in case that no ID value is + given).
+ + +
+ + This operation gets all device server certificates (including self-signed) for the purpose of TLS + authentication and all device client certificates for the purpose of IEEE 802.1X authentication. + This command lists only the TLS server certificates and IEEE 802.1X client certificates for the + device (neither trusted CA certificates nor trusted root certificates). The certificates are + returned as binary data. A device that supports TLS shall support this command and the + certificates shall be encoded using ASN.1 [X.681], [X.682], [X.683] DER [X.690] encoding + rules. + + + + + This operation is specific to TLS functionality. This operation gets the status + (enabled/disabled) of the device TLS server certificates. A device that supports TLS shall + support this command. + + + + + This operation is specific to TLS functionality. This operation sets the status (enable/disable) + of the device TLS server certificates. A device that supports TLS shall support this command. + Typically only one device server certificate is allowed to be enabled at a time. + + + + + This operation deletes a certificate or multiple certificates. The device MAY also delete a + private/public key pair which is coupled with the certificate to be deleted. The device that + support either TLS or IEEE 802.1X shall support the deletion of a certificate or multiple + certificates through this command. Either all certificates are deleted successfully or a fault + message shall be returned without deleting any certificate. + + + + + This operation requests a PKCS #10 certificate signature request from the device. The + returned information field shall be either formatted exactly as specified in [PKCS#10] or PEM + encoded [PKCS#10] format. In order for this command to work, the device must already have + a private/public key pair. This key pair should be referred by CertificateID as specified in the + input parameter description. This CertificateID refers to the key pair generated using + CreateCertificate command.
+ A device that support onboard key pair generation that supports either TLS or IEEE 802.1X + using client certificate shall support this command.
+ + +
+ + TLS server certificate(s) or IEEE 802.1X client certificate(s) created using the PKCS#10 + certificate request command can be loaded into the device using this command (see Section + 8.4.13). The certificate ID in the request shall be present. The device may sort the received + certificate(s) based on the public key and subject information in the certificate(s). + The certificate ID in the request will be the ID value the client wish to have. The device is + supposed to scan the generated key pairs present in the device to identify which is the + correspondent key pair with the loaded certificate and then make the link between the + certificate and the key pair.
+ A device that supports onboard key pair generation that support either TLS or IEEE 802.1X + shall support this command.
+ The certificates shall be encoded using ASN.1 [X.681], [X.682], [X.683] DER [X.690] encoding + rules.
+ This command is applicable to any device type, although the parameter name is called for + historical reasons NVTCertificate.
+ + +
+ + This operation is specific to TLS functionality. This operation gets the status + (enabled/disabled) of the device TLS client authentication. A device that supports TLS shall + support this command. + + + + + This operation is specific to TLS functionality. This operation sets the status + (enabled/disabled) of the device TLS client authentication. A device that supports TLS shall + support this command. + + + + + This operation gets a list of all available relay outputs and their settings.
+ This method has been depricated with version 2.0. Refer to the DeviceIO service.
+ + +
+ + This operation sets the settings of a relay output. +
This method has been depricated with version 2.0. Refer to the DeviceIO service.
+ + +
+ + This operation sets the state of a relay output. +
This method has been depricated with version 2.0. Refer to the DeviceIO service.
+ + +
+ + Manage auxiliary commands supported by a device, such as controlling an Infrared (IR) lamp, + a heater or a wiper or a thermometer that is connected to the device.
+ The supported commands can be retrieved via the AuxiliaryCommands capability.
+ Although the name of the auxiliary commands can be freely defined, commands starting with the prefix tt: are + reserved to define frequently used commands and these reserved commands shall all share the "tt:command|parameter" syntax. +
    +
  • tt:Wiper|On – Request to start the wiper.
  • +
  • tt:Wiper|Off – Request to stop the wiper.
  • +
  • tt:Washer|On – Request to start the washer.
  • +
  • tt:Washer|Off – Request to stop the washer.
  • +
  • tt:WashingProcedure|On – Request to start the washing procedure.
  • +
  • tt: WashingProcedure |Off – Request to stop the washing procedure.
  • +
  • tt:IRLamp|On – Request to turn ON an IR illuminator attached to the unit.
  • +
  • tt:IRLamp|Off – Request to turn OFF an IR illuminator attached to the unit.
  • +
  • tt:IRLamp|Auto – Request to configure an IR illuminator attached to the unit so that it automatically turns ON and OFF.
  • +
+ A device that indicates auxiliary service capability shall support this command.
+ + +
+ + CA certificates will be loaded into a device and be used for the sake of following two cases. + The one is for the purpose of TLS client authentication in TLS server function. The other one + is for the purpose of Authentication Server authentication in IEEE 802.1X function. This + operation gets all CA certificates loaded into a device. A device that supports either TLS client + authentication or IEEE 802.1X shall support this command and the returned certificates shall + be encoded using ASN.1 [X.681], [X.682], [X.683] DER [X.690] encoding rules. + + + + + There might be some cases that a Certificate Authority or some other equivalent creates a + certificate without having PKCS#10 certificate signing request. In such cases, the certificate + will be bundled in conjunction with its private key. This command will be used for such use + case scenarios. The certificate ID in the request is optionally set to the ID value the client + wish to have. If the certificate ID is not specified in the request, device can choose the ID + accordingly.
+ This operation imports a private/public key pair into the device. + The certificates shall be encoded using ASN.1 [X.681], [X.682], [X.683] DER [X.690] encoding + rules.
+ A device that does not support onboard key pair generation and support either TLS or IEEE + 802.1X using client certificate shall support this command. A device that support onboard key + pair generation MAY support this command. The security policy of a device that supports this + operation should make sure that the private key is sufficiently protected.
+ + +
+ + This operation requests the information of a certificate specified by certificate ID. The device + should respond with its “Issuer DN”, “Subject DN”, “Key usage”, "Extended key usage”, “Key + Length”, “Version”, “Serial Number”, “Signature Algorithm” and “Validity” data as the + information of the certificate, as long as the device can retrieve such information from the + specified certificate.
+ A device that supports either TLS or IEEE 802.1X should support this command.
+ + +
+ + This command is used when it is necessary to load trusted CA certificates or trusted root + certificates for the purpose of verification for its counterpart i.e. client certificate verification in + TLS function or server certificate verification in IEEE 802.1X function.
+ A device that support either TLS or IEEE 802.1X shall support this command. As for the + supported certificate format, either DER format or PEM format is possible to be used. But a + device that support this command shall support at least DER format as supported format type. + The device may sort the received certificate(s) based on the public key and subject + information in the certificate(s). Either all CA certificates are loaded successfully or a fault + message shall be returned without loading any CA certificate.
+ + +
+ + This operation newly creates IEEE 802.1X configuration parameter set of the device. The + device shall support this command if it supports IEEE 802.1X. If the device receives this + request with already existing configuration token (Dot1XConfigurationToken) specification, the + device should respond with 'ter:ReferenceToken ' error to indicate there is some configuration + conflict. + + + + + While the CreateDot1XConfiguration command is trying to create a new configuration + parameter set, this operation modifies existing IEEE 802.1X configuration parameter set of + the device. A device that support IEEE 802.1X shall support this command. + + + + + This operation gets one IEEE 802.1X configuration parameter set from the device by + specifying the configuration token (Dot1XConfigurationToken).
+ A device that supports IEEE 802.1X shall support this command. + Regardless of whether the 802.1X method in the retrieved configuration has a password or + not, the device shall not include the Password element in the response.
+ + +
+ + This operation gets all the existing IEEE 802.1X configuration parameter sets from the device. + The device shall respond with all the IEEE 802.1X configurations so that the client can get to + know how many IEEE 802.1X configurations are existing and how they are configured.
+ A device that support IEEE 802.1X shall support this command.
+ Regardless of whether the 802.1X method in the retrieved configuration has a password or + not, the device shall not include the Password element in the response.
+ + +
+ + This operation deletes an IEEE 802.1X configuration parameter set from the device. Which + configuration should be deleted is specified by the 'Dot1XConfigurationToken' in the request. + A device that support IEEE 802.1X shall support this command. + + + + + This operation returns the IEEE802.11 capabilities. The device shall support + this operation. + + + + + This operation returns the status of a wireless network interface. The device shall support this + command. + + + + + This operation returns a lists of the wireless networks in range of the device. A device should + support this operation. + + + + + This operation is used to retrieve URIs from which system information may be downloaded + using HTTP. URIs may be returned for the following system information:
+ System Logs. Multiple system logs may be returned, of different types. The exact format of + the system logs is outside the scope of this specification.
+ Support Information. This consists of arbitrary device diagnostics information from a device. + The exact format of the diagnostic information is outside the scope of this specification.
+ System Backup. The received file is a backup file that can be used to restore the current + device configuration at a later date. The exact format of the backup configuration file is + outside the scope of this specification.
+ If the device allows retrieval of system logs, support information or system backup data, it + should make them available via HTTP GET. If it does, it shall support the GetSystemUris + command.
+ + +
+ + This operation initiates a firmware upgrade using the HTTP POST mechanism. The response + to the command includes an HTTP URL to which the upgrade file may be uploaded. The + actual upgrade takes place as soon as the HTTP POST operation has completed. The device + should support firmware upgrade through the StartFirmwareUpgrade command. The exact + format of the firmware data is outside the scope of this specification. + Firmware upgrade over HTTP may be achieved using the following steps:
    +
  1. Client calls StartFirmwareUpgrade.
  2. +
  3. Server responds with upload URI and optional delay value.
  4. +
  5. Client waits for delay duration if specified by server.
  6. +
  7. Client transmits the firmware image to the upload URI using HTTP POST.
  8. +
  9. Server reprograms itself using the uploaded image, then reboots.
  10. +
+ If the firmware upgrade fails because the upgrade file was invalid, the HTTP POST response + shall be “415 Unsupported Media Type”. If the firmware upgrade fails due to an error at the + device, the HTTP POST response shall be “500 Internal Server Error”.
+ The value of the Content-Type header in the HTTP POST request shall be “application/octetstream”.
+ + +
+ + This operation initiates a system restore from backed up configuration data using the HTTP + POST mechanism. The response to the command includes an HTTP URL to which the backup + file may be uploaded. The actual restore takes place as soon as the HTTP POST operation + has completed. Devices should support system restore through the StartSystemRestore + command. The exact format of the backup configuration data is outside the scope of this + specification.
+ System restore over HTTP may be achieved using the following steps:
    +
  1. Client calls StartSystemRestore.
  2. +
  3. Server responds with upload URI.
  4. +
  5. Client transmits the configuration data to the upload URI using HTTP POST.
  6. +
  7. Server applies the uploaded configuration, then reboots if necessary.
  8. +
+ If the system restore fails because the uploaded file was invalid, the HTTP POST response + shall be “415 Unsupported Media Type”. If the system restore fails due to an error at the + device, the HTTP POST response shall be “500 Internal Server Error”.
+ The value of the Content-Type header in the HTTP POST request shall be “application/octetstream”.
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/onvif/wsdl/media.wsdl b/onvif/wsdl/media.wsdl new file mode 100644 index 000000000..de3fdb30d --- /dev/null +++ b/onvif/wsdl/media.wsdl @@ -0,0 +1,3688 @@ + + + + + + + + + + + + + + + + + + + + The capabilities for the media service is returned in the Capabilities element. + + + + + + + + + + + Media profile capabilities. + + + + + Streaming capabilities. + + + + + + + Indicates if GetSnapshotUri is supported. + + + + + Indicates whether or not Rotation feature is supported. + + + + + Indicates the support for changing video source mode. + + + + + Indicates if OSD is supported. + + + + + + + + + + + + + Maximum number of profiles supported. + + + + + + + + + + + + Indicates support for RTP multicast. + + + + + Indicates support for RTP over TCP. + + + + + Indicates support for RTP/RTSP/TCP. + + + + + Indicates support for non aggregate RTSP control. + + + + + Indicates the device does not support live media streaming via RTSP. + + + + + + + + + + + + + + + + + List of existing Video Sources + + + + + + + + + + + + + + + + + + List of existing Audio Sources + + + + + + + + + + + + + + + + + + List of existing Audio Outputs + + + + + + + + + + + + friendly name of the profile to be created + + + + + Optional token, specifying the unique identifier of the new profile.
A device supports at least a token length of 12 characters and characters "A-Z" | "a-z" | "0-9" | "-.".
+
+
+
+
+
+ + + + + + returns the new created profile + + + + + + + + + + + + this command requests a specific profile + + + + + + + + + + + returns the requested media profile + + + + + + + + + + + + + + + + + + + lists all profiles that exist in the media service + + + + + + + + + + + + Reference to the profile where the configuration should be added + + + + + Contains a reference to the VideoEncoderConfiguration to add + + + + + + + + + + + + + + + + + + Contains a reference to the media profile from which the +VideoEncoderConfiguration shall be removed. + + + + + + + + + + + + + + + + + + Reference to the profile where the configuration should be added + + + + + Contains a reference to the VideoSourceConfiguration to add + + + + + + + + + + + + + + + + + + Contains a reference to the media profile from which the +VideoSourceConfiguration shall be removed. + + + + + + + + + + + + + + + + + + Reference to the profile where the configuration should be added + + + + + Contains a reference to the AudioEncoderConfiguration to add + + + + + + + + + + + + + + + + + + Contains a reference to the media profile from which the +AudioEncoderConfiguration shall be removed. + + + + + + + + + + + + + + + + + + Reference to the profile where the configuration should be added + + + + + Contains a reference to the AudioSourceConfiguration to add + + + + + + + + + + + + + + + + + + Contains a reference to the media profile from which the +AudioSourceConfiguration shall be removed. + + + + + + + + + + + + + + + + + + Reference to the profile where the configuration should be added + + + + + Contains a reference to the PTZConfiguration to add + + + + + + + + + + + + + + + + + + Contains a reference to the media profile from which the +PTZConfiguration shall be removed. + + + + + + + + + + + + + + + + + + Reference to the profile where the configuration should be added + + + + + Contains a reference to the VideoAnalyticsConfiguration to add + + + + + + + + + + + + + + + + + + Contains a reference to the media profile from which the +VideoAnalyticsConfiguration shall be removed. + + + + + + + + + + + + + + + + + + Reference to the profile where the configuration should be added + + + + + Contains a reference to the MetadataConfiguration to add + + + + + + + + + + + + + + + + + + Contains a reference to the media profile from which the +MetadataConfiguration shall be removed. + + + + + + + + + + + + + + + + + + Reference to the profile where the configuration should be added + + + + + Contains a reference to the AudioOutputConfiguration to add + + + + + + + + + + + + + + + + + + Contains a reference to the media profile from which the +AudioOutputConfiguration shall be removed. + + + + + + + + + + + + + + + + + + This element contains a reference to the profile where the configuration should be added. + + + + + This element contains a reference to the AudioDecoderConfiguration to add. + + + + + + + + + + + + + + + + + + This element contains a reference to the media profile from which the AudioDecoderConfiguration shall be removed. + + + + + + + + + + + + + + + + + + This element contains a reference to the profile that should be deleted. + + + + + + + + + + + + + + + + + + + + + + + + + + This element contains a list of video encoder configurations. + + + + + + + + + + + + + + + + + + This element contains a list of video source configurations. + + + + + + + + + + + + + + + + + + This element contains a list of audio encoder configurations. + + + + + + + + + + + + + + + + + + This element contains a list of audio source configurations. + + + + + + + + + + + + + + + + + + This element contains a list of VideoAnalytics configurations. + + + + + + + + + + + + + + + + + + This element contains a list of metadata configurations + + + + + + + + + + + + + + + + + + + This element contains a list of audio output configurations + + + + + + + + + + + + + + + + + + This element contains a list of audio decoder configurations + + + + + + + + + + + + Token of the requested video source configuration. + + + + + + + + + + + + The requested video source configuration. + + + + + + + + + + + + Token of the requested video encoder configuration. + + + + + + + + + + + The requested video encoder configuration. + + + + + + + + + + + + Token of the requested audio source configuration. + + + + + + + + + + + The requested audio source configuration. + + + + + + + + + + + + Token of the requested audio encoder configuration. + + + + + + + + + + + The requested audio encoder configuration + + + + + + + + + + + + Token of the requested video analytics configuration. + + + + + + + + + + + The requested video analytics configuration. + + + + + + + + + + + + Token of the requested metadata configuration. + + + + + + + + + + + The requested metadata configuration. + + + + + + + + + + + + + Token of the requested audio output configuration. + + + + + + + + + + + + The requested audio output configuration. + + + + + + + + + + + + Token of the requested audio decoder configuration. + + + + + + + + + + + + The requested audio decoder configuration + + + + + + + + + + + + Contains the token of an existing media profile the configurations shall be compatible with. + + + + + + + + + + + Contains a list of video encoder configurations that are compatible with the specified media profile. + + + + + + + + + + + + Contains the token of an existing media profile the configurations shall be compatible with. + + + + + + + + + + + Contains a list of video source configurations that are compatible with the specified media profile. + + + + + + + + + + + + Contains the token of an existing media profile the configurations shall be compatible with. + + + + + + + + + + + Contains a list of audio encoder configurations that are compatible with the specified media profile. + + + + + + + + + + + + Contains the token of an existing media profile the configurations shall be compatible with. + + + + + + + + + + + Contains a list of audio source configurations that are compatible with the specified media profile. + + + + + + + + + + + + Contains the token of an existing media profile the configurations shall be compatible with. + + + + + + + + + + + Contains a list of video analytics configurations that are compatible with the specified media profile. + + + + + + + + + + + + Contains the token of an existing media profile the configurations shall be compatible with. + + + + + + + + + + + Contains a list of metadata configurations that are compatible with the specified media profile. + + + + + + + + + + + + + Contains the token of an existing media profile the configurations shall be compatible with. + + + + + + + + + + + Contains a list of audio output configurations that are compatible with the specified media profile. + + + + + + + + + + + + Contains the token of an existing media profile the configurations shall be compatible with. + + + + + + + + + + + Contains a list of audio decoder configurations that are compatible with the specified media profile. + + + + + + + + + + + + + + + + + Contains the modified video encoder configuration. The configuration shall exist in the device. + + + + + The ForcePersistence element is obsolete and should always be assumed to be true. + + + + + + + + + + + + + + + + + + Contains the modified video source configuration. The configuration shall exist in the device. + + + + + The ForcePersistence element is obsolete and should always be assumed to be true. + + + + + + + + + + + + + + + + + + Contains the modified audio encoder configuration. The configuration shall exist in the device. + + + + + The ForcePersistence element is obsolete and should always be assumed to be true. + + + + + + + + + + + + + + + + + + Contains the modified audio source configuration. The configuration shall exist in the device. + + + + + The ForcePersistence element is obsolete and should always be assumed to be true. + + + + + + + + + + + + + + + + + + Contains the modified video analytics configuration. The configuration shall exist in the device. + + + + + The ForcePersistence element is obsolete and should always be assumed to be true. + + + + + + + + + + + + + + + + + + Contains the modified metadata configuration. The configuration shall exist in the device. + + + + + The ForcePersistence element is obsolete and should always be assumed to be true. + + + + + + + + + + + + + + + + + + + Contains the modified audio output configuration. The configuration shall exist in the device. + + + + + The ForcePersistence element is obsolete and should always be assumed to be true. + + + + + + + + + + + + + + + + + + Contains the modified audio decoder configuration. The configuration shall exist in the device. + + + + + The ForcePersistence element is obsolete and should always be assumed to be true. + + + + + + + + + + + + + + + + + + Optional video source configurationToken that specifies an existing configuration that the options are intended for. + + + + + Optional ProfileToken that specifies an existing media profile that the options shall be compatible with. + + + + + + + + + + + This message contains the video source configuration options. If a video source configuration is specified, the options shall concern that particular configuration. If a media profile is specified, the options shall be compatible with that media profile. If no tokens are specified, the options shall be considered generic for the device. + + + + + + + + + + + + Optional video encoder configuration token that specifies an existing configuration that the options are intended for. + + + + + Optional ProfileToken that specifies an existing media profile that the options shall be compatible with. + + + + + + + + + + + + + + + + + + + Optional audio source configuration token that specifies an existing configuration that the options are intended for. + + + + + Optional ProfileToken that specifies an existing media profile that the options shall be compatible with. + + + + + + + + + + + This message contains the audio source configuration options. If a audio source configuration is specified, the options shall concern that particular configuration. If a media profile is specified, the options shall be compatible with that media profile. If no tokens are specified, the options shall be considered generic for the device. + + + + + + + + + + + + Optional audio encoder configuration token that specifies an existing configuration that the options are intended for. + + + + + Optional ProfileToken that specifies an existing media profile that the options shall be compatible with. + + + + + + + + + + + This message contains the audio encoder configuration options. If a audio encoder configuration is specified, the options shall concern that particular configuration. If a media profile is specified, the options shall be compatible with that media profile. If no tokens are specified, the options shall be considered generic for the device. + + + + + + + + + + + + Optional metadata configuration token that specifies an existing configuration that the options are intended for. + + + + + Optional ProfileToken that specifies an existing media profile that the options shall be compatible with. + + + + + + + + + + + This message contains the metadata configuration options. If a metadata configuration is specified, the options shall concern that particular configuration. If a media profile is specified, the options shall be compatible with that media profile. If no tokens are specified, the options shall be considered generic for the device. + + + + + + + + + + + + Optional audio output configuration token that specifies an existing configuration that the options are intended for. + + + + + Optional ProfileToken that specifies an existing media profile that the options shall be compatible with. + + + + + + + + + + + This message contains the audio output configuration options. If a audio output configuration is specified, the options shall concern that particular configuration. If a media profile is specified, the options shall be compatible with that media profile. If no tokens are specified, the options shall be considered generic for the device. + + + + + + + + + + + + Optional audio decoder configuration token that specifies an existing configuration that the options are intended for. + + + + + Optional ProfileToken that specifies an existing media profile that the options shall be compatible with. + + + + + + + + + + + This message contains the audio decoder configuration options. If a audio decoder configuration is specified, the options shall concern that particular configuration. If a media profile is specified, the options shall be compatible with that media profile. If no tokens are specified, the options shall be considered generic for the device. + + + + + + + + + + + + Token of the video source configuration + + + + + + + + + + + The minimum guaranteed total number of encoder instances (applications) per VideoSourceConfiguration. The device is able to deliver the TotalNumber of streams + + + + + If a device limits the number of instances for respective Video Codecs the response contains the information how many Jpeg streams can be set up at the same time per VideoSource. + + + + + If a device limits the number of instances for respective Video Codecs the response contains the information how many H264 streams can be set up at the same time per VideoSource. + + + + + If a device limits the number of instances for respective Video Codecs the response contains the information how many Mpeg4 streams can be set up at the same time per VideoSource. + + + + + + + + + + + + Stream Setup that should be used with the uri + + + + + The ProfileToken element indicates the media profile to use and will define the configuration of the content of the stream. + + + + + + + + + + + + + + + + + + + + + + + Contains the token of the Profile that is used to define the multicast stream. + + + + + + + + + + + + + + + + + + Contains the token of the Profile that is used to define the multicast stream. + + + + + + + + + + + + + + + + + + Contains a Profile reference for which a Synchronization Point is requested. + + + + + + + + + + + + + + + + + + The ProfileToken element indicates the media profile to use and will define the source and dimensions of the snapshot. + + + + + + + + + + + + + + + + + + + + + + + Contains a video source reference for which a video source mode is requested. + + + + + + + + + + + Return the information for specified video source mode. + + + + + + + + + + + + Contains a video source reference for which a video source mode is requested. + + + + + Indicate video source mode. + + + + + + + + + + + The response contains information about rebooting after returning response. When Reboot is set true, a device will reboot automatically after setting mode. + + + + + + + + + Indication which encodings are supported for this video source. The list may contain one or more enumeration values of tt:VideoEncoding. + + + + + + + + + Max frame rate in frames per second for this video source mode. + + + + + Max horizontal and vertical resolution for this video source mode. + + + + + Indication which encodings are supported for this video source. The list may contain one or more enumeration values of tt:VideoEncoding. + + + + + After setting the mode if a device starts to reboot this value is true. If a device change the mode without rebooting this value is false. If true, configured parameters may not be guaranteed by the device after rebooting. + + + + + Informative description of this video source mode. This field should be described in English. + + + + + + + Indicate token for video source mode. + + + + + Indication of whether this mode is active. If active this value is true. In case of non-indication, it means as false. The value of true shall be had by only one video source mode. + + + + + + + + + + + + + + + + + + + Token of the Video Source Configuration, which has OSDs associated with are requested. If token not exist, request all available OSDs. + + + + + + + + + + + This element contains a list of requested OSDs. + + + + + + + + + + + + The GetOSD command fetches the OSD configuration if the OSD token is known. + + + + + + + + + + + + The requested OSD configuration. + + + + + + + + + + + + + Contains the modified OSD configuration. + + + + + + + + + + + + + + + + + + + + Video Source Configuration Token that specifies an existing video source configuration that the options shall be compatible with. + + + + + + + + + + + + + + + + + + + + + + + + + Contain the initial OSD configuration for create. + + + + + + + + + + + + Returns Token of the newly created OSD + + + + + + + + + + + + + This element contains a reference to the OSD configuration that should be deleted. + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Returns the capabilities of the media service. The result is returned in a typed answer. + + + + + + This command lists all available physical video inputs of the device. + + + + + This command lists all available physical audio inputs of the device. + + + + + This command lists all available physical audio outputs of the device. + + + + + + This operation creates a new empty media profile. The media profile shall be created in the +device and shall be persistent (remain after reboot). A created profile shall be deletable and a device shall set the “fixed” attribute to false in the +returned Profile. + + + + + If the profile token is already known, a profile can be fetched through the GetProfile command. + + + + + Any endpoint can ask for the existing media profiles of a device using the GetProfiles +command. Pre-configured or dynamically configured profiles can be retrieved using this +command. This command lists all configured profiles in a device. The client does not need to +know the media profile in order to use the command. + + + + + This operation adds a VideoEncoderConfiguration to an existing media profile. If a +configuration exists in the media profile, it will be replaced. The change shall be persistent. A device shall +support adding a compatible VideoEncoderConfiguration to a Profile containing a VideoSourceConfiguration and shall +support streaming video data of such a profile. + + + + + + This operation removes a VideoEncoderConfiguration from an existing media profile. If the +media profile does not contain a VideoEncoderConfiguration, the operation has no effect. The removal shall be persistent. + + + + + This operation adds a VideoSourceConfiguration to an existing media profile. If such a +configuration exists in the media profile, it will be replaced. The change shall be persistent. + + + + + This operation removes a VideoSourceConfiguration from an existing media profile. If the +media profile does not contain a VideoSourceConfiguration, the operation has no effect. The removal shall be persistent. Video source configurations should only be removed after removing a +VideoEncoderConfiguration from the media profile. + + + + + This operation adds an AudioEncoderConfiguration to an existing media profile. If a +configuration exists in the media profile, it will be replaced. The change shall be persistent. A device shall +support adding a compatible AudioEncoderConfiguration to a profile containing an AudioSourceConfiguration and shall +support streaming audio data of such a profile. + + + + + + This operation removes an AudioEncoderConfiguration from an existing media profile. If the +media profile does not contain an AudioEncoderConfiguration, the operation has no effect. +The removal shall be persistent. + + + + + This operation adds an AudioSourceConfiguration to an existing media profile. If a +configuration exists in the media profile, it will be replaced. The change shall be persistent. + + + + + This operation removes an AudioSourceConfiguration from an existing media profile. If the +media profile does not contain an AudioSourceConfiguration, the operation has no effect. The +removal shall be persistent. Audio source configurations should only be removed after removing an +AudioEncoderConfiguration from the media profile. + + + + + This operation adds a PTZConfiguration to an existing media profile. If a configuration exists +in the media profile, it will be replaced. The change shall be persistent. Adding a PTZConfiguration to a media profile means that streams using that media profile can +contain PTZ status (in the metadata), and that the media profile can be used for controlling +PTZ movement. + + + + + This operation removes a PTZConfiguration from an existing media profile. If the media profile +does not contain a PTZConfiguration, the operation has no effect. The removal shall be persistent. + + + + + This operation adds a VideoAnalytics configuration to an existing media profile. If a +configuration exists in the media profile, it will be replaced. The change shall be persistent. Adding a VideoAnalyticsConfiguration to a media profile means that streams using that media +profile can contain video analytics data (in the metadata) as defined by the submitted configuration reference. A profile containing only a video analytics configuration but no video source configuration is incomplete. Therefore, a client should first add a video source configuration to a profile before adding a video analytics configuration. The device can deny adding of a video analytics +configuration before a video source configuration. + + + + + This operation removes a VideoAnalyticsConfiguration from an existing media profile. If the media profile does not contain a VideoAnalyticsConfiguration, the operation has no effect. +The removal shall be persistent. + + + + + This operation adds a Metadata configuration to an existing media profile. If a configuration exists in the media profile, it will be replaced. The change shall be persistent. Adding a MetadataConfiguration to a Profile means that streams using that profile contain metadata. Metadata can consist of events, PTZ status, and/or video analytics data. + + + + + This operation removes a MetadataConfiguration from an existing media profile. If the media profile does not contain a MetadataConfiguration, the operation has no effect. The removal shall be persistent. + + + + + This operation adds an AudioOutputConfiguration to an existing media profile. If a configuration exists in the media profile, it will be replaced. The change shall be persistent. + + + + + This operation removes an AudioOutputConfiguration from an existing media profile. If the media profile does not contain an AudioOutputConfiguration, the operation has no effect. The removal shall be persistent. + + + + + This operation adds an AudioDecoderConfiguration to an existing media profile. If a configuration exists in the media profile, it shall be replaced. The change shall be persistent. + + + + + This operation removes an AudioDecoderConfiguration from an existing media profile. If the media profile does not contain an AudioDecoderConfiguration, the operation has no effect. The removal shall be persistent. + + + + + This operation deletes a profile. This change shall always be persistent. Deletion of a profile is only possible for non-fixed profiles + + + + + + This operation lists all existing video source configurations for a device. The client need not know anything about the video source configurations in order to use the command. + + + + + This operation lists all existing video encoder configurations of a device. This command lists all configured video encoder configurations in a device. The client need not know anything apriori about the video encoder configurations in order to use the command. + + + + + This operation lists all existing audio source configurations of a device. This command lists all audio source configurations in a device. The client need not know anything apriori about the audio source configurations in order to use the command. + + + + + This operation lists all existing device audio encoder configurations. The client need not know anything apriori about the audio encoder configurations in order to use the command. + + + + + This operation lists all video analytics configurations of a device. This command lists all configured video analytics in a device. The client need not know anything apriori about the video analytics in order to use the command. + + + + + This operation lists all existing metadata configurations. The client need not know anything apriori about the metadata in order to use the command. + + + + + This command lists all existing AudioOutputConfigurations of a device. The NVC need not know anything apriori about the audio configurations to use this command. + + + + + This command lists all existing AudioDecoderConfigurations of a device. The NVC need not know anything apriori about the audio decoder configurations in order to +use this command. + + + + + If the video source configuration token is already known, the video source configuration can be fetched through the GetVideoSourceConfiguration command. + + + + + If the video encoder configuration token is already known, the encoder configuration can be fetched through the GetVideoEncoderConfiguration command. + + + + + The GetAudioSourceConfiguration command fetches the audio source configurations if the audio source configuration token is already known. An + + + + + The GetAudioEncoderConfiguration command fetches the encoder configuration if the audio encoder configuration token is known. + + + + + The GetVideoAnalyticsConfiguration command fetches the video analytics configuration if the video analytics token is known. + + + + + The GetMetadataConfiguration command fetches the metadata configuration if the metadata token is known. + + + + + If the audio output configuration token is already known, the output configuration can be fetched through the GetAudioOutputConfiguration command. + + + + + If the audio decoder configuration token is already known, the decoder configuration can be fetched through the GetAudioDecoderConfiguration command. + + + + + + This operation lists all the video encoder configurations of the device that are compatible with a certain media profile. Each of the returned configurations shall be a valid input parameter for the AddVideoEncoderConfiguration command on the media profile. The result will vary depending on the capabilities, configurations and settings in the device. + + + + + This operation requests all the video source configurations of the device that are compatible +with a certain media profile. Each of the returned configurations shall be a valid input +parameter for the AddVideoSourceConfiguration command on the media profile. The result +will vary depending on the capabilities, configurations and settings in the device. + + + + + This operation requests all audio encoder configurations of a device that are compatible with a certain media profile. Each of the returned configurations shall be a valid input parameter for the AddAudioSourceConfiguration command on the media profile. The result varies depending on the capabilities, configurations and settings in the device. + + + + + This operation requests all audio source configurations of the device that are compatible with a certain media profile. Each of the returned configurations shall be a valid input parameter for the AddAudioEncoderConfiguration command on the media profile. The result varies depending on the capabilities, configurations and settings in the device. + + + + + This operation requests all video analytic configurations of the device that are compatible with a certain media profile. Each of the returned configurations shall be a valid input parameter for the AddVideoAnalyticsConfiguration command on the media profile. The result varies depending on the capabilities, configurations and settings in the device. + + + + + This operation requests all the metadata configurations of the device that are compatible with a certain media profile. Each of the returned configurations shall be a valid input parameter for the AddMetadataConfiguration command on the media profile. The result varies depending on the capabilities, configurations and settings in the device. + + + + + This command lists all audio output configurations of a device that are compatible with a certain media profile. Each returned configuration shall be a valid input for the +AddAudioOutputConfiguration command. + + + + + This operation lists all the audio decoder configurations of the device that are compatible with a certain media profile. Each of the returned configurations shall be a valid input parameter for the AddAudioDecoderConfiguration command on the media profile. + + + + + + This operation modifies a video source configuration. The ForcePersistence flag indicates if the changes shall remain after reboot of the device. Running streams using this configuration may be immediately updated according to the new settings. The changes are not guaranteed to take effect unless the client requests a new stream URI and restarts any affected stream. NVC methods for changing a running stream are out of scope for this specification. + + + + + This operation modifies a video encoder configuration. The ForcePersistence flag indicates if the changes shall remain after reboot of the device. Changes in the Multicast settings shall always be persistent. Running streams using this configuration may be immediately updated according to the new settings. The changes are not guaranteed to take effect unless the client requests a new stream URI and restarts any affected stream. NVC methods for changing a running stream are out of scope for this specification.
SessionTimeout is provided as a hint for keeping rtsp session by a device. If necessary the device may adapt parameter values for SessionTimeout elements without returning an error. For the time between keep alive calls the client shall adhere to the timeout value signaled via RTSP.
+ + +
+ + This operation modifies an audio source configuration. The ForcePersistence flag indicates if +the changes shall remain after reboot of the device. Running streams using this configuration +may be immediately updated according to the new settings. The changes are not guaranteed +to take effect unless the client requests a new stream URI and restarts any affected stream +NVC methods for changing a running stream are out of scope for this specification. + + + + + This operation modifies an audio encoder configuration. The ForcePersistence flag indicates if +the changes shall remain after reboot of the device. Running streams using this configuration may be immediately updated +according to the new settings. The changes are not guaranteed to take effect unless the client +requests a new stream URI and restarts any affected streams. NVC methods for changing a +running stream are out of scope for this specification. + + + + + A video analytics configuration is modified using this command. The ForcePersistence flag +indicates if the changes shall remain after reboot of the device or not. Running streams using +this configuration shall be immediately updated according to the new settings. Otherwise +inconsistencies can occur between the scene description processed by the rule engine and +the notifications produced by analytics engine and rule engine which reference the very same +video analytics configuration token. + + + + + This operation modifies a metadata configuration. The ForcePersistence flag indicates if the +changes shall remain after reboot of the device. Changes in the Multicast settings shall +always be persistent. Running streams using this configuration may be updated immediately +according to the new settings. The changes are not guaranteed to take effect unless the client +requests a new stream URI and restarts any affected streams. NVC methods for changing a +running stream are out of scope for this specification. + + + + + This operation modifies an audio output configuration. The ForcePersistence flag indicates if +the changes shall remain after reboot of the device. + + + + + This operation modifies an audio decoder configuration. The ForcePersistence flag indicates if +the changes shall remain after reboot of the device. + + + + + + This operation returns the available options (supported values and ranges for video source configuration parameters) when the video source parameters are +reconfigured If a video source configuration is specified, the options shall concern that +particular configuration. If a media profile is specified, the options shall be compatible with +that media profile. + + + + + This operation returns the available options (supported values and ranges for video encoder + configuration parameters) when the video encoder parameters are reconfigured.
+ For JPEG, MPEG4 and H264 extension elements have been defined that provide additional information. A device must provide the + XxxOption information for all encodings supported and should additionally provide the corresponding XxxOption2 information.
+ This response contains the available video encoder configuration options. If a video encoder configuration is specified, + the options shall concern that particular configuration. If a media profile is specified, the options shall be + compatible with that media profile. If no tokens are specified, the options shall be considered generic for the device. +
+ + +
+ + This operation returns the available options (supported values and ranges for audio source configuration parameters) when the audio source parameters are +reconfigured. If an audio source configuration is specified, the options shall concern that +particular configuration. If a media profile is specified, the options shall be compatible with +that media profile. + + + + + This operation returns the available options (supported values and ranges for audio encoder configuration parameters) when the audio encoder parameters are +reconfigured. + + + + + This operation returns the available options (supported values and ranges for metadata configuration parameters) for changing the metadata configuration. + + + + + This operation returns the available options (supported values and ranges for audio output configuration parameters) for configuring an audio output. + + + + + This command list the audio decoding capabilities for a given profile and configuration of a +device. + + + + + + The GetGuaranteedNumberOfVideoEncoderInstances command can be used to request the +minimum number of guaranteed video encoder instances (applications) per Video Source +Configuration. + + + + + + This operation requests a URI that can be used to initiate a live media stream using RTSP as +the control protocol. The returned URI shall remain valid indefinitely even if the profile is +changed. The ValidUntilConnect, ValidUntilReboot and Timeout Parameter shall be set +accordingly (ValidUntilConnect=false, ValidUntilReboot=false, timeout=PT0S).
+ The correct syntax for the StreamSetup element for these media stream setups defined in 5.1.1 of the streaming specification are as follows: +
    +
  1. RTP unicast over UDP: StreamType = "RTP_unicast", TransportProtocol = "UDP"
  2. +
  3. RTP over RTSP over HTTP over TCP: StreamType = "RTP_unicast", TransportProtocol = "HTTP"
  4. +
  5. RTP over RTSP over TCP: StreamType = "RTP_unicast", TransportProtocol = "RTSP"
  6. +
+
+If a multicast stream is requested the VideoEncoderConfiguration, AudioEncoderConfiguration and MetadataConfiguration element inside the corresponding +media profile must be configured with valid multicast settings.
+For full compatibility with other ONVIF services a device should not generate Uris longer than +128 octets.
+ + +
+ + This command starts multicast streaming using a specified media profile of a device. +Streaming continues until StopMulticastStreaming is called for the same Profile. The +streaming shall continue after a reboot of the device until a StopMulticastStreaming request is +received. The multicast address, port and TTL are configured in the +VideoEncoderConfiguration, AudioEncoderConfiguration and MetadataConfiguration +respectively. + + + + + This command stop multicast streaming using a specified media profile of a device + + + + + Synchronization points allow clients to decode and correctly use all data after the +synchronization point. +For example, if a video stream is configured with a large I-frame distance and a client loses a +single packet, the client does not display video until the next I-frame is transmitted. In such +cases, the client can request a Synchronization Point which enforces the device to add an I-Frame as soon as possible. Clients can request Synchronization Points for profiles. The device +shall add synchronization points for all streams associated with this profile. +Similarly, a synchronization point is used to get an update on full PTZ or event status through +the metadata stream. +If a video stream is associated with the profile, an I-frame shall be added to this video stream. +If a PTZ metadata stream is associated to the profile, +the PTZ position shall be repeated within the metadata stream. + + + + + A client uses the GetSnapshotUri command to obtain a JPEG snapshot from the +device. The returned URI shall remain valid indefinitely even if the profile is changed. The +ValidUntilConnect, ValidUntilReboot and Timeout Parameter shall be set accordingly +(ValidUntilConnect=false, ValidUntilReboot=false, timeout=PT0S). The URI can be used for +acquiring a JPEG image through a HTTP GET operation. The image encoding will always be +JPEG regardless of the encoding setting in the media profile. The Jpeg settings +(like resolution or quality) may be taken from the profile if suitable. The provided +image will be updated automatically and independent from calls to GetSnapshotUri. + + + + + A device returns the information for current video source mode and settable video source modes of specified video source. A device that indicates a capability of VideoSourceModes shall support this command. + + + + + SetVideoSourceMode changes the media profile structure relating to video source for the specified video source mode. A device that indicates a capability of VideoSourceModes shall support this command. The behavior after changing the mode is not defined in this specification. + + + + + + Get the OSDs. + + + + + Get the OSD. + + + + + Get the OSD Options. + + + + + Set the OSD + + + + + Create the OSD. + + + + + Delete the OSD. + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/onvif/wsdl/onvif-local.xsd b/onvif/wsdl/onvif-local.xsd new file mode 100644 index 000000000..17f722d6e --- /dev/null +++ b/onvif/wsdl/onvif-local.xsd @@ -0,0 +1,8578 @@ + + + + + + + + + + + + + + Base class for physical entities like inputs and outputs. + + + + Unique identifier referencing the physical entity. + + + + + + + Unique identifier for a physical or logical resource. + Tokens should be assigned such that they are unique within a device. Tokens must be at least unique within its class. + Length up to 64 characters. + + + + + + + + + User readable name. Length up to 64 characters. + + + + + + + + + Rectangle defined by lower left corner position and size. Units are pixel. + + + + + + + + + + Range of a rectangle. The rectangle itself is defined by lower left corner position and size. Units are pixel. + + + + + Range of X-axis. + + + + + Range of Y-axis. + + + + + Range of width. + + + + + Range of height. + + + + + + + + Range of values greater equal Min value and less equal Max value. + + + + + + + + + + Range of values greater equal Min value and less equal Max value. + + + + + + + + + + Range of duration greater equal Min duration and less equal Max duration. + + + + + + + + + + List of values. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Representation of a physical video input. + + + + + + + Frame rate in frames per second. + + + + + Horizontal and vertical resolution + + + + + Optional configuration of the image sensor. + + + + + + + + + + + + + + + Optional configuration of the image sensor. To be used if imaging service 2.00 is supported. + + + + + + + + + + + + + + + Representation of a physical audio input. + + + + + + + number of available audio channels. (1: mono, 2: stereo) + + + + + + + + + + + + + A media profile consists of a set of media configurations. Media profiles are used by a client + to configure properties of a media stream from an NVT.
+ An NVT shall provide at least one media profile at boot. An NVT should provide “ready to use” + profiles for the most common media configurations that the device offers.
+ A profile consists of a set of interconnected configuration entities. Configurations are provided + by the NVT and can be either static or created dynamically by the NVT. For example, the + dynamic configurations can be created by the NVT depending on current available encoding + resources. +
+
+ + + + User readable name of the profile. + + + + + Optional configuration of the Video input. + + + + + Optional configuration of the Audio input. + + + + + Optional configuration of the Video encoder. + + + + + Optional configuration of the Audio encoder. + + + + + Optional configuration of the video analytics module and rule engine. + + + + + Optional configuration of the pan tilt zoom unit. + + + + + Optional configuration of the metadata stream. + + + + + Extensions defined in ONVIF 2.0 + + + + + + Unique identifier of the profile. + + + + + A value of true signals that the profile cannot be deleted. Default is false. + + + +
+ + + + + + + Optional configuration of the Audio output. + + + + + Optional configuration of the Audio decoder. + + + + + + + + + + + + + + + + + + + + + + + + + + Base type defining the common properties of a configuration. + + + + + User readable name. Length up to 64 characters. + + + + + Number of internal references currently using this configuration.
This parameter is read-only and cannot be changed by a set request.
For example the value increases if the configuration is added to a media profile or attached to a PaneConfiguration.
+
+
+
+ + + Token that uniquely refernces this configuration. Length up to 64 characters. + + +
+ + + + + + + + + + Reference to the physical input. + + + + + Rectangle specifying the Video capturing area. The capturing area shall not be larger than the whole Video source area. + + + + + + + + + + + + + + + Optional element to configure rotation of captured image. + + + + + + + + + + + + + + + + + Parameter to enable/disable Rotation feature. + + + + + Optional parameter to configure how much degree of clockwise rotation of image for On mode. Omitting this parameter for On mode means 180 degree rotation. + + + + + + + + + + + + + + + + + + + + + + + + + + Supported range for the capturing area. + + + + + List of physical inputs. + + + + + + + + + + + + + Options of parameters for Rotation feature. + + + + + + + + + + + + + + + + + Supported options of Rotate mode parameter. + + + + + List of supported degree value for rotation. + + + + + + + + + + + + + + + + + + + + + + Used video codec, either Jpeg, H.264 or Mpeg4 + + + + + Configured video resolution + + + + + Relative value for the video quantizers and the quality of the video. A high value within supported quality range means higher quality + + + + + Optional element to configure rate control related parameters. + + + + + Optional element to configure Mpeg4 related parameters. + + + + + Optional element to configure H.264 related parameters. + + + + + Defines the multicast settings that could be used for video streaming. + + + + + The rtsp session timeout for the related video stream + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Number of the columns of the Video image. + + + + + Number of the lines of the Video image. + + + + + + + + + + Maximum output framerate in fps. If an EncodingInterval is provided the resulting encoded framerate will be reduced by the given factor. + + + + + Interval at which images are encoded and transmitted. (A value of 1 means that every frame is encoded, a value of 2 means that every 2nd frame is encoded ...) + + + + + the maximum output bitrate in kbps + + + + + + + + + + Determines the interval in which the I-Frames will be coded. An entry of 1 indicates I-Frames are continuously generated. An entry of 2 indicates that every 2nd image is an I-Frame, and 3 only every 3rd frame, etc. The frames in between are coded as P or B Frames. + + + + + the Mpeg4 profile, either simple profile (SP) or advanced simple profile (ASP) + + + + + + + + + + Group of Video frames length. Determines typically the interval in which the I-Frames will be coded. An entry of 1 indicates I-Frames are continuously generated. An entry of 2 indicates that every 2nd image is an I-Frame, and 3 only every 3rd frame, etc. The frames in between are coded as P or B Frames. + + + + + the H.264 profile, either baseline, main, extended or high + + + + + + + + + + Range of the quality values. A high value means higher quality. + + + + + Optional JPEG encoder settings ranges (See also Extension element). + + + + + Optional MPEG-4 encoder settings ranges (See also Extension element). + + + + + Optional H.264 encoder settings ranges (See also Extension element). + + + + + + + + + + + + + Optional JPEG encoder settings ranges. + + + + + Optional MPEG-4 encoder settings ranges. + + + + + Optional H.264 encoder settings ranges. + + + + + + + + + + + + + + + + + List of supported image sizes. + + + + + Supported frame rate in fps (frames per second). + + + + + Supported encoding interval range. The encoding interval corresponds to the number of frames devided by the encoded frames. An encoding interval value of "1" means that all frames are encoded. + + + + + + + + + + + + Supported range of encoded bitrate in kbps. + + + + + + + + + + + + + + List of supported image sizes. + + + + + Supported group of Video frames length. This value typically corresponds to the I-Frame distance. + + + + + Supported frame rate in fps (frames per second). + + + + + Supported encoding interval range. The encoding interval corresponds to the number of frames devided by the encoded frames. An encoding interval value of "1" means that all frames are encoded. + + + + + List of supported MPEG-4 profiles. + + + + + + + + + + + + Supported range of encoded bitrate in kbps. + + + + + + + + + + + + + + List of supported image sizes. + + + + + Supported group of Video frames length. This value typically corresponds to the I-Frame distance. + + + + + Supported frame rate in fps (frames per second). + + + + + Supported encoding interval range. The encoding interval corresponds to the number of frames devided by the encoded frames. An encoding interval value of "1" means that all frames are encoded. + + + + + List of supported H.264 profiles. + + + + + + + + + + + + Supported range of encoded bitrate in kbps. + + + + + + + + + + + + + + + + + + Token of the Audio Source the configuration applies to + + + + + + + + + + + + + + Tokens of the audio source the configuration can be used for. + + + + + + + + + + + + + + + + + + + + + + Audio codec used for encoding the audio input (either G.711, G.726 or AAC) + + + + + The output bitrate in kbps. + + + + + The output sample rate in kHz. + + + + + Defines the multicast settings that could be used for video streaming. + + + + + The rtsp session timeout for the related audio stream + + + + + + + + + + + + + + + + + + + + + + list of supported AudioEncoderConfigurations + + + + + + + + + + + The enoding used for audio data (either G.711, G.726 or AAC) + + + + + List of supported bitrates in kbps for the specified Encoding + + + + + List of supported Sample Rates in kHz for the specified Encoding + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + optional element to configure which PTZ related data is to include in the metadata stream + + + + + + Defines whether the streamed metadata will include metadata from the analytics engines (video, cell motion, audio etc.) + + + + + Defines the multicast settings that could be used for video streaming. + + + + + The rtsp session timeout for the related audio stream + + + + + + + + + + + + + + + + + + + + + + True if the metadata stream shall contain the PTZ status (IDLE, MOVING or UNKNOWN) + + + + + True if the metadata stream shall contain the PTZ position + + + + + + + + + Subcription handling in the same way as base notification subscription. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True if the device is able to stream pan or tilt status information. + + + + + True if the device is able to stream zoom status inforamtion. + + + + + + True if the device is able to stream the pan or tilt position. + + + + + True if the device is able to stream zoom position information. + + + + + + + + + + + + + + + + + + Representation of a physical video outputs. + + + + + + + + Resolution of the display in Pixel. + + + + + Refresh rate of the display in Hertz. + + + + + Aspect ratio of the display as physical extent of width divided by height. + + + + + + + + + + + + + + + + + + + + + + + + Token of the Video Output the configuration applies to + + + + + + + + + + + + + + + + + + + + + + + + + If the device is able to decode Jpeg streams this element describes the supported codecs and configurations + + + + + If the device is able to decode H.264 streams this element describes the supported codecs and configurations + + + + + If the device is able to decode Mpeg4 streams this element describes the supported codecs and configurations + + + + + + + + + + + + List of supported H.264 Video Resolutions + + + + + List of supported H264 Profiles (either baseline, main, extended or high) + + + + + Supported H.264 bitrate range in kbps + + + + + Supported H.264 framerate range in fps + + + + + + + + + + + + List of supported Jpeg Video Resolutions + + + + + Supported Jpeg bitrate range in kbps + + + + + Supported Jpeg framerate range in fps + + + + + + + + + + + + List of supported Mpeg4 Video Resolutions + + + + + List of supported Mpeg4 Profiles (either SP or ASP) + + + + + Supported Mpeg4 bitrate range in kbps + + + + + Supported Mpeg4 framerate range in fps + + + + + + + + + + + + + + + + + + Representation of a physical audio outputs. + + + + + + + + + + + + + + + + + + + + Token of the phsycial Audio output. + + + + + + An audio channel MAY support different types of audio transmission. While for full duplex + operation no special handling is required, in half duplex operation the transmission direction + needs to be switched. + The optional SendPrimacy parameter inside the AudioOutputConfiguration indicates which + direction is currently active. An NVC can switch between different modes by setting the + AudioOutputConfiguration.
+ The following modes for the Send-Primacy are defined:
    +
  • www.onvif.org/ver20/HalfDuplex/Server + The server is allowed to send audio data to the client. The client shall not send + audio data via the backchannel to the NVT in this mode.
  • +
  • www.onvif.org/ver20/HalfDuplex/Client + The client is allowed to send audio data via the backchannel to the server. The + NVT shall not send audio data to the client in this mode.
  • +
  • www.onvif.org/ver20/HalfDuplex/Auto + It is up to the device how to deal with sending and receiving audio data.
  • +
+ Acoustic echo cancellation is out of ONVIF scope.
+
+
+ + + Volume setting of the output. The applicable range is defined via the option AudioOutputOptions.OutputLevelRange. + + + +
+ +
+
+
+ + + + + + + + Tokens of the physical Audio outputs (typically one). + + + + + + An audio channel MAY support different types of audio transmission. While for full duplex + operation no special handling is required, in half duplex operation the transmission direction + needs to be switched. + The optional SendPrimacy parameter inside the AudioOutputConfiguration indicates which + direction is currently active. An NVC can switch between different modes by setting the + AudioOutputConfiguration.
+ The following modes for the Send-Primacy are defined:
    +
  • www.onvif.org/ver20/HalfDuplex/Server + The server is allowed to send audio data to the client. The client shall not send + audio data via the backchannel to the NVT in this mode.
  • +
  • www.onvif.org/ver20/HalfDuplex/Client + The client is allowed to send audio data via the backchannel to the server. The + NVT shall not send audio data to the client in this mode.
  • +
  • www.onvif.org/ver20/HalfDuplex/Auto + It is up to the device how to deal with sending and receiving audio data.
  • +
+ Acoustic echo cancellation is out of ONVIF scope.
+
+
+ + + Minimum and maximum level range supported for this Output. + + + +
+ +
+ + + + + + The Audio Decoder Configuration does not contain any that parameter to configure the +decoding .A decoder shall decode every data it receives (according to its capabilities). + + + + + + + + + + + + + + + + + + If the device is able to decode AAC encoded audio this section describes the supported configurations + + + + + If the device is able to decode G711 encoded audio this section describes the supported configurations + + + + + If the device is able to decode G726 encoded audio this section describes the supported configurations + + + + + + + + + + + + List of supported bitrates in kbps + + + + + List of supported sample rates in kHz + + + + + + + + + + + + List of supported bitrates in kbps + + + + + List of supported sample rates in kHz + + + + + + + + + + + + List of supported bitrates in kbps + + + + + List of supported sample rates in kHz + + + + + + + + + + + + + + + + + + + + The multicast address (if this address is set to 0 no multicast streaming is enaled) + + + + + The RTP mutlicast destination port. A device may support RTCP. In this case the port value shall be even to allow the corresponding RTCP stream to be mapped to the next higher (odd) destination port number as defined in the RTSP specification. + + + + + In case of IPv6 the TTL value is assumed as the hop limit. Note that for IPV6 and administratively scoped IPv4 multicast the primary use for hop limit / TTL is to prevent packets from (endlessly) circulating and not limiting scope. In these cases the address contains the scope. + + + + + Read only property signalling that streaming is persistant. Use the methods StartMulticastStreaming and StopMulticastStreaming to switch its state. + + + + + + + + + + + + Defines if a multicast or unicast stream is requested + + + + + + + + + + + + + + + + + + + + Defines the network protocol for streaming, either UDP=RTP/UDP, RTSP=RTP/RTSP/TCP or HTTP=RTP/RTSP/HTTP/TCP + + + + + + + + + + + + + + + + + + + + Stable Uri to be used for requesting the media stream + + + + + Indicates if the Uri is only valid until the connection is established. The value shall be set to "false". + + + + + Indicates if the Uri is invalid after a reboot of the device. The value shall be set to "false". + + + + + Duration how long the Uri is valid. This parameter shall be set to PT0S to indicate that this stream URI is indefinitely valid even if the profile changes + + + + + + + + + + + + + + + + + + + + + + + + + Indicates if the scope is fixed or configurable. + + + + + Scope item URI. + + + + + + + + + + + + + + + + + + + + + + + + + Indicates whether or not an interface is enabled. + + + + + Network interface information + + + + + Link configuration. + + + + + IPv4 network interface configuration. + + + + + IPv6 network interface configuration. + + + + + + + + + + + + + + + + Extension point prepared for future 802.3 configuration. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configured link settings. + + + + + Current active link settings. + + + + + Integer indicating interface type, for example: 6 is ethernet. + + + + + + + + + + Auto negotiation on/off. + + + + + Speed. + + + + + Duplex type, Half or Full. + + + + + + + + + + + + + + + + + For valid numbers, please refer to http://www.iana.org/assignments/ianaiftype-mib. + + + + + + + + + + Network interface name, for example eth0. + + + + + Network interface MAC address. + + + + + Maximum transmission unit. + + + + + + + + + + Indicates whether or not IPv6 is enabled. + + + + + IPv6 configuration. + + + + + + + + + + Indicates whether or not IPv4 is enabled. + + + + + IPv4 configuration. + + + + + + + + + + List of manually added IPv4 addresses. + + + + + Link local address. + + + + + IPv4 address configured by using DHCP. + + + + + Indicates whether or not DHCP is used. + + + + + + + + + + + + Indicates whether router advertisment is used. + + + + + DHCP configuration. + + + + + List of manually entered IPv6 addresses. + + + + + List of link local IPv6 addresses. + + + + + List of IPv6 addresses configured by using DHCP. + + + + + List of IPv6 addresses configured by using router advertisment. + + + + + + + + + + + + + + + + + + + + + + + + + + + Network protocol type string. + + + + + Indicates if the protocol is enabled or not. + + + + + The port that is used by the protocol. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Network host type: IPv4, IPv6 or DNS. + + + + + IPv4 address. + + + + + IPv6 address. + + + + + DNS name. + + + + + + + + + + + + + + + + + + Indicates if the address is an IPv4 or IPv6 address. + + + + + IPv4 address. + + + + + IPv6 address + + + + + + + + + + IPv4 address + + + + + Prefix/submask length + + + + + + + + + + + + + + IPv6 address + + + + + Prefix/submask length + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Indicates whether the hostname is obtained from DHCP or not. + + + + + Indicates the hostname. + + + + + + + + + + + + + + + + + + Indicates whether or not DNS information is retrieved from DHCP. + + + + + Search domain. + + + + + List of DNS addresses received from DHCP. + + + + + List of manually entered DNS addresses. + + + + + + + + + + + + + + + + + + Indicates if NTP information is to be retrieved by using DHCP. + + + + + List of NTP addresses retrieved by using DHCP. + + + + + List of manually entered NTP addresses. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Dynamic DNS type. + + + + + DNS name. + + + + + Time to live. + + + + + + + + + + + + + + + + + + + + + + + + + + Indicates whether or not an interface is enabled. + + + + + Link configuration. + + + + + Maximum transmission unit. + + + + + IPv4 network interface configuration. + + + + + IPv6 network interface configuration. + + + + + + + + + + + + + + + + + + + + + Indicates whether or not IPv6 is enabled. + + + + + Indicates whether router advertisment is used. + + + + + List of manually added IPv6 addresses. + + + + + DHCP configuration. + + + + + + + + + + Indicates whether or not IPv4 is enabled. + + + + + List of manually added IPv4 addresses. + + + + + Indicates whether or not DHCP is used. + + + + + + + + + + IPv4 address string. + + + + + IPv6 address string. + + + + + + + + + + Unique identifier of network interface. + + + + + Indicates whether the zero-configuration is enabled or not. + + + + + The zero-configuration IPv4 address(es) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + According to IEEE802.11-2007 H.4.1 the RSNA PSK consists of 256 bits, or 64 octets when represented in hex
+ Either Key or Passphrase shall be given, if both are supplied Key shall be used by the device and Passphrase ignored. +
+
+
+ + + + According to IEEE802.11-2007 H.4.1 a pass-phrase is a sequence of between 8 and 63 ASCII-encoded characters and + each character in the pass-phrase must have an encoding in the range of 32 to 126 (decimal),inclusive.
+ If only Passpharse is supplied the Key shall be derived using the algorithm described in IEEE802.11-2007 section H.4 +
+
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + See IEEE802.11 7.3.2.25.2 for details. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Analytics capabilities + + + + + Device capabilities + + + + + Event capabilities + + + + + Imaging capabilities + + + + + Media capabilities + + + + + PTZ capabilities + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Analytics service URI. + + + + + Indicates whether or not rules are supported. + + + + + Indicates whether or not modules are supported. + + + + + + + + + + + + Device service URI. + + + + + Network capabilities. + + + + + System capabilities. + + + + + I/O capabilities. + + + + + Security capabilities. + + + + + + + + + + + + + + + + + + Event service URI. + + + + + Indicates whether or not WS Subscription policy is supported. + + + + + Indicates whether or not WS Pull Point is supported. + + + + + Indicates whether or not WS Pausable Subscription Manager Interface is supported. + + + + + + + + + + + + Number of input connectors. + + + + + Number of relay outputs. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Media service URI. + + + + + Streaming capabilities. + + + + + + + + + + + + + + + + + + + + + Indicates whether or not RTP multicast is supported. + + + + + Indicates whether or not RTP over TCP is supported. + + + + + Indicates whether or not RTP/RTSP/TCP is supported. + + + + + + + + + + + + + + + + + + Maximum number of profiles. + + + + + + + + + + + + Indicates whether or not IP filtering is supported. + + + + + Indicates whether or not zeroconf is supported. + + + + + Indicates whether or not IPv6 is supported. + + + + + Indicates whether or not is supported. + + + + + + + + + + + + + + + + + + + + + + + + + + Indicates whether or not TLS 1.1 is supported. + + + + + Indicates whether or not TLS 1.2 is supported. + + + + + Indicates whether or not onboard key generation is supported. + + + + + Indicates whether or not access policy configuration is supported. + + + + + Indicates whether or not WS-Security X.509 token is supported. + + + + + Indicates whether or not WS-Security SAML token is supported. + + + + + Indicates whether or not WS-Security Kerberos token is supported. + + + + + Indicates whether or not WS-Security REL token is supported. + + + + + + + + + + + + + + + + + + + + + EAP Methods supported by the device. The int values refer to the IANA EAP Registry. + + + + + + + + + + + + Indicates whether or not WS Discovery resolve requests are supported. + + + + + Indicates whether or not WS-Discovery Bye is supported. + + + + + Indicates whether or not remote discovery is supported. + + + + + Indicates whether or not system backup is supported. + + + + + Indicates whether or not system logging is supported. + + + + + Indicates whether or not firmware upgrade is supported. + + + + + Indicates supported ONVIF version(s). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Major version number. + + + + + Two digit minor version number (e.g. X.0.1 maps to "01" and X.2.1 maps to "21" where X stands for Major version number). + + + + + + + + + + Imaging service URI. + + + + + + + + + + + PTZ service URI. + + + + + + + + + + + + + + + + + + + + + + + + + + Indication that the SetLayout command supports only predefined layouts. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The address of the replay service. + + + + + + + + + + + + The address of the receiver service. + + + + + Indicates whether the device can receive RTP multicast streams. + + + + + Indicates whether the device can receive RTP/TCP streams + + + + + Indicates whether the device can receive RTP/RTSP/TCP streams. + + + + + The maximum number of receivers supported by the device. + + + + + The maximum allowed length for RTSP URIs. + + + + + + + + + + + + + Obsolete property. + + + + + + + + + + + + + + + + + + + + + + + Enumeration describing the available system log modes. + + + + + Indicates that a system log is requested. + + + + + Indicates that a access log is requested. + + + + + + + + + + The log information as attachment data. + + + + + The log information as character data. + + + + + + + + + + The support information as attachment data. + + + + + The support information as character data. + + + + + + + + + + base64 encoded binary data. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Enumeration describing the available factory default modes. + + + + + Indicates that a hard factory default is requested. + + + + + Indicates that a soft factory default is requested. + + + + + + + + + + Indicates that the date and time are set manually. + + + + + Indicates that the date and time are set through NTP + + + + + + + + General date time inforamtion returned by the GetSystemDateTime method. + + + + + Indicates if the time is set manully or through NTP. + + + + + Informative indicator whether daylight savings is currently on/off. + + + + + Timezone information in Posix format. + + + + + Current system date and time in UTC format. This field is mandatory since version 2.0. + + + + + Date and time in local format. + + + + + + + + + + + + + + + + + + + + + + + + + + Range is 1 to 12. + + + + + Range is 1 to 31. + + + + + + + + + + Range is 0 to 23. + + + + + Range is 0 to 59. + + + + + Range is 0 to 61 (typically 59). + + + + + + + + + The TZ format is specified by POSIX, please refer to POSIX 1003.1 section 8.3
+ Example: Europe, Paris TZ=CET-1CEST,M3.5.0/2,M10.5.0/3
+ CET = designation for standard time when daylight saving is not in force
+ -1 = offset in hours = negative so 1 hour east of Greenwich meridian
+ CEST = designation when daylight saving is in force ("Central European Summer Time")
+ , = no offset number between code and comma, so default to one hour ahead for daylight saving
+ M3.5.0 = when daylight saving starts = the last Sunday in March (the "5th" week means the last in the month)
+ /2, = the local time when the switch occurs = 2 a.m. in this case
+ M10.5.0 = when daylight saving ends = the last Sunday in October.
+ /3, = the local time when the switch occurs = 3 a.m. in this case
+
+
+ + + + Posix timezone string. + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Username string. + + + + + Password string. + + + + + User level string. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Certificate id. + + + + + base64 encoded DER representation of certificate. + + + + + + + + + + Certificate id. + + + + + Indicates whether or not a certificate is used in a HTTPS configuration. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Validity Range is from "NotBefore" to "NotAfter"; the corresponding DateTimeRange is from "From" to "Until" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + EAP Method type as defined in IANA EAP Registry. + + + + + + + + + + + + + + + + + + + + + Confgiuration information for TLS Method. + + + + + Password for those EAP Methods that require a password. The password shall never be returned on a get method. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 'Bistable' or 'Monostable' +
    +
  • Bistable – After setting the state, the relay remains in this state.
  • +
  • Monostable – After setting the state, the relay returns to its idle state after the specified time.
  • +
+
+
+
+ + + Time after which the relay returns to its idle state if it is in monostable mode. If the Mode field is set to bistable mode the value of the parameter can be ignored. + + + + + + 'open' or 'closed' +
    +
  • 'open' means that the relay is open when the relay state is set to 'inactive' through the trigger command and closed when the state is set to 'active' through the same command.
  • +
  • 'closed' means that the relay is closed when the relay state is set to 'inactive' through the trigger command and open when the state is set to 'active' through the same command.
  • +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A unique identifier that is used to reference PTZ Nodes. + + + + + + + A list of Coordinate Systems available for the PTZ Node. For each Coordinate System, the PTZ Node MUST specify its allowed range. + + + + + + + All preset operations MUST be available for this PTZ Node if one preset is supported. + + + + + + + A boolean operator specifying the availability of a home position. If set to true, the Home Position Operations MUST be available for this PTZ Node. + + + + + + + A list of supported Auxiliary commands. If the list is not empty, the Auxiliary Operations MUST be available for this PTZ Node. + + + + + + + + + Indication whether the HomePosition of a Node is fixed or it can be changed via the SetHomePosition command. + + + + + + + + + + + + + + + Detail of supported Preset Tour feature. + + + + + + + + + + + + + + + + + + Indicates number of preset tours that can be created. Required preset tour operations shall be available for this PTZ Node if one or more preset tour is supported. + + + + + Indicates which preset tour operations are available for this PTZ Node. + + + + + + + + + + + + + + + + + + + + + A mandatory reference to the PTZ Node that the PTZ Configuration belongs to. + + + + + + + If the PTZ Node supports absolute Pan/Tilt movements, it shall specify one Absolute Pan/Tilt Position Space as default. + + + + + + + If the PTZ Node supports absolute zoom movements, it shall specify one Absolute Zoom Position Space as default. + + + + + + + If the PTZ Node supports relative Pan/Tilt movements, it shall specify one RelativePan/Tilt Translation Space as default. + + + + + + + If the PTZ Node supports relative zoom movements, it shall specify one Relative Zoom Translation Space as default. + + + + + + + If the PTZ Node supports continuous Pan/Tilt movements, it shall specify one Continuous Pan/Tilt Velocity Space as default. + + + + + + + If the PTZ Node supports continuous zoom movements, it shall specify one Continuous Zoom Velocity Space as default. + + + + + + + If the PTZ Node supports absolute or relative PTZ movements, it shall specify corresponding default Pan/Tilt and Zoom speeds. + + + + + + + If the PTZ Node supports continuous movements, it shall specify a default timeout, after which the movement stops. + + + + + + + The Pan/Tilt limits element should be present for a PTZ Node that supports an absolute Pan/Tilt. If the element is present it signals the support for configurable Pan/Tilt limits. If limits are enabled, the Pan/Tilt movements shall always stay within the specified range. The Pan/Tilt limits are disabled by setting the limits to –INF or +INF. + + + + + + + The Zoom limits element should be present for a PTZ Node that supports absolute zoom. If the element is present it signals the supports for configurable Zoom limits. If limits are enabled the zoom movements shall always stay within the specified range. The Zoom limits are disabled by settings the limits to -INF and +INF. + + + + + + + + + + + + + + + + + + + + + Optional element to configure PT Control Direction related features. + + + + + + + + + + + + + + + + + Optional element to configure related parameters for E-Flip. + + + + + Optional element to configure related parameters for reversing of PT Control Direction. + + + + + + + + + + + + + + + + + + + Parameter to enable/disable E-Flip feature. + + + + + + + + + + + + Parameter to enable/disable Reverse feature. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A list of supported coordinate systems including their range limitations. + + + + + + + A timeout Range within which Timeouts are accepted by the PTZ Node. + + + + + + + Supported options for PT Direction Control. + + + + + + + + + + + + + + + + + + Supported options for EFlip feature. + + + + + Supported options for Reverse feature. + + + + + + + + + + + + + + + + + + Options of EFlip mode parameter. + + + + + + + + + + + + + + + + + + Options of Reverse mode parameter. + + + + + + + + + + + + + + + + + + + A range of pan tilt limits. + + + + + + + + + + + + A range of zoom limit + + + + + + + + + + + + The Generic Pan/Tilt Position space is provided by every PTZ node that supports absolute Pan/Tilt, since it does not relate to a specific physical range. + Instead, the range should be defined as the full range of the PTZ unit normalized to the range -1 to 1 resulting in the following space description. + + + + + + + The Generic Zoom Position Space is provided by every PTZ node that supports absolute Zoom, since it does not relate to a specific physical range. + Instead, the range should be defined as the full range of the Zoom normalized to the range 0 (wide) to 1 (tele). + There is no assumption about how the generic zoom range is mapped to magnification, FOV or other physical zoom dimension. + + + + + + + The Generic Pan/Tilt translation space is provided by every PTZ node that supports relative Pan/Tilt, since it does not relate to a specific physical range. + Instead, the range should be defined as the full positive and negative translation range of the PTZ unit normalized to the range -1 to 1, + where positive translation would mean clockwise rotation or movement in right/up direction resulting in the following space description. + + + + + + + The Generic Zoom Translation Space is provided by every PTZ node that supports relative Zoom, since it does not relate to a specific physical range. + Instead, the corresponding absolute range should be defined as the full positive and negative translation range of the Zoom normalized to the range -1 to1, + where a positive translation maps to a movement in TELE direction. The translation is signed to indicate direction (negative is to wide, positive is to tele). + There is no assumption about how the generic zoom range is mapped to magnification, FOV or other physical zoom dimension. This results in the following space description. + + + + + + + The generic Pan/Tilt velocity space shall be provided by every PTZ node, since it does not relate to a specific physical range. + Instead, the range should be defined as a range of the PTZ unit’s speed normalized to the range -1 to 1, where a positive velocity would map to clockwise + rotation or movement in the right/up direction. A signed speed can be independently specified for the pan and tilt component resulting in the following space description. + + + + + + + The generic zoom velocity space specifies a zoom factor velocity without knowing the underlying physical model. The range should be normalized from -1 to 1, + where a positive velocity would map to TELE direction. A generic zoom velocity space description resembles the following. + + + + + + + The speed space specifies the speed for a Pan/Tilt movement when moving to an absolute position or to a relative translation. + In contrast to the velocity spaces, speed spaces do not contain any directional information. The speed of a combined Pan/Tilt + movement is represented by a single non-negative scalar value. + + + + + + + The speed space specifies the speed for a Zoom movement when moving to an absolute position or to a relative translation. + In contrast to the velocity spaces, speed spaces do not contain any directional information. + + + + + + + + + + + + + + + + + + + + A URI of coordinate systems. + + + + + + + A range of x-axis. + + + + + + + A range of y-axis. + + + + + + + + + + + + A URI of coordinate systems. + + + + + + + A range of x-axis. + + + + + + + + + + + + + Pan/tilt coordinate space selector. The following options are defined:
    +
  • http://www.onvif.org/ver10/tptz/PanTiltSpaces/PositionGenericSpace
  • +
  • http://www.onvif.org/ver10/tptz/PanTiltSpaces/TranslationGenericSpace
  • +
  • http://www.onvif.org/ver10/tptz/PanTiltSpaces/VelocityGenericSpace
  • +
  • http://www.onvif.org/ver10/tptz/PanTiltSpaces/GenericSpeedSpace
  • +
+
+
+
+
+ + + + + + + Pan/tilt coordinate space selector. The following options are defined:
    +
  • http://www.onvif.org/ver10/tptz/PanTiltSpaces/PositionGenericSpace
  • +
  • http://www.onvif.org/ver10/tptz/PanTiltSpaces/TranslationGenericSpace
  • +
  • http://www.onvif.org/ver10/tptz/PanTiltSpaces/VelocityGenericSpace
  • +
  • http://www.onvif.org/ver10/tptz/PanTiltSpaces/GenericSpeedSpace
  • +
+
+
+
+
+ + + + + + Pan and tilt position. The x component corresponds to pan and the y component to tilt. + + + + + + A zoom position. + + + + + + + + + + + Pan and tilt speed. The x component corresponds to pan and the y component to tilt. If omitted in a request, the current (if any) PanTilt movement should not be affected. + + + + + + A zoom speed. If omitted in a request, the current (if any) Zoom movement should not be affected. + + + + + + + + + + + + Specifies the absolute position of the PTZ unit together with the Space references. The default absolute spaces of the corresponding PTZ configuration MUST be referenced within the Position element. + + + + + + + Indicates if the Pan/Tilt/Zoom device unit is currently moving, idle or in an unknown state. + + + + + + + States a current PTZ error. + + + + + + + Specifies the UTC time when this status was generated. + + + + + + + + + + + + + + A list of preset position name. + + + + + + + A list of preset position. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Readable name of the preset tour. + + + + + Read only parameters to indicate the status of the preset tour. + + + + + Auto Start flag of the preset tour. True allows the preset tour to be activated always. + + + + + Parameters to specify the detail behavior of the preset tour. + + + + + A list of detail of touring spots including preset positions. + + + + + + + Unique identifier of this preset tour. + + + + + + + + + + + + + + + + Detail definition of preset position of the tour spot. + + + + + Optional parameter to specify Pan/Tilt and Zoom speed on moving toward this tour spot. + + + + + Optional parameter to specify time duration of staying on this tour sport. + + + + + + + + + + + + + + + + + + + Option to specify the preset position with Preset Token defined in advance. + + + + + Option to specify the preset position with the home position of this PTZ Node. "False" to this parameter shall be treated as an invalid argument. + + + + + Option to specify the preset position with vector of PTZ node directly. + + + + + + + + + + + + + + + + + + + + Indicates state of this preset tour by Idle/Touring/Paused. + + + + + Indicates a tour spot currently staying. + + + + + + + + + + + + + + + + + + Optional parameter to specify how many times the preset tour is recurred. + + + + + Optional parameter to specify how long time duration the preset tour is recurred. + + + + + Optional parameter to choose which direction the preset tour goes. Forward shall be chosen in case it is omitted. + + + + + + + + + + + + + + + + + + Indicates whether or not the AutoStart is supported. + + + + + Supported options for Preset Tour Starting Condition. + + + + + Supported options for Preset Tour Spot. + + + + + + + + + + + + Supported options for detail definition of preset position of the tour spot. + + + + + Supported range of stay time for a tour spot. + + + + + + + + + + + + A list of available Preset Tokens for tour spots. + + + + + An option to indicate Home postion for tour spots. + + + + + Supported range of Pan and Tilt for tour spots. + + + + + Supported range of Zoom for a tour spot. + + + + + + + + + + + + + + + + + + Supported range of Recurring Time. + + + + + Supported range of Recurring Duration. + + + + + Supported options for Direction of Preset Tour. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Status of focus position. + + + + + + + Status of focus MoveStatus. + + + + + + + Error status of focus. + + + + + + + + + + + + + + + Parameter to set autofocus near limit (unit: meter). + + + + + Parameter to set autofocus far limit (unit: meter). +If set to 0.0, infinity will be used. + + + + + + + + + + + + + + + + + + + Enabled/disabled BLC mode (on/off). + + + + + Image brightness (unit unspecified). + + + + + Color saturation of the image (unit unspecified). + + + + + Contrast of the image (unit unspecified). + + + + + Exposure mode of the device. + + + + + Focus configuration. + + + + + Infrared Cutoff Filter settings. + + + + + Sharpness of the Video image. + + + + + WDR settings. + + + + + White balance settings. + + + + + + + + + + + + + + + + + + + Exposure Mode +
    +
  • Auto – Enabled the exposure algorithm on the NVT.
  • +
  • Manual – Disabled exposure algorithm on the NVT.
  • +
+
+
+
+ + + + The exposure priority mode (low noise/framerate). + + + + + + + Rectangular exposure mask. + + + + + + + Minimum value of exposure time range allowed to be used by the algorithm. + + + + + + + Maximum value of exposure time range allowed to be used by the algorithm. + + + + + + + Minimum value of the sensor gain range that is allowed to be used by the algorithm. + + + + + + + Maximum value of the sensor gain range that is allowed to be used by the algorithm. + + + + + + + Minimum value of the iris range allowed to be used by the algorithm. + + + + + + + Maximum value of the iris range allowed to be used by the algorithm. + + + + + + + The fixed exposure time used by the image sensor (μs). + + + + + + + The fixed gain used by the image sensor (dB). + + + + + + + The fixed attenuation of input light affected by the iris (dB). 0dB maps to a fully opened iris. + + + +
+
+ + + + + + + + + + + + + + White dynamic range (on/off) + + + + + + + Optional level parameter (unitless) + + + + + + + + + Enumeration describing the available backlight compenstation modes. + + + + + Backlight compensation is disabled. + + + + + Backlight compensation is enabled. + + + + + + + + + + Backlight compensation mode (on/off). + + + + + Optional level parameter (unit unspecified). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Parameters for the absolute focus control. + + + + + + + Parameters for the relative focus control. + + + + + + + Parameter for the continuous focus control. + + + + + + + + + + + + Position parameter for the absolute focus control. + + + + + + + Speed parameter for the absolute focus control. + + + + + + + + + + + + Distance parameter for the relative focus control. + + + + + + + Speed parameter for the relative focus control. + + + + + + + + + + + + Speed parameter for the Continuous focus control. + + + + + + + + + + + + + + + + + + + + Valid ranges of the position. + + + + + + + Valid ranges of the speed. + + + + + + + + + + + + Valid ranges of the distance. + + + + + + + Valid ranges of the speed. + + + + + + + + + + + + Valid ranges of the speed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Auto whitebalancing mode (auto/manual). + + + + + Rgain (unitless). + + + + + Bgain (unitless). + + + + + + + + + + + + + + + + + + Status of focus. + + + + + + + + + + + + + + + + + + + + Status of focus position. + + + + + + + Status of focus MoveStatus. + + + + + + + Error status of focus. + + + + + + + + + + + + + + + + + Type describing the ImagingSettings of a VideoSource. The supported options and ranges can be obtained via the GetOptions command. + + + + + Enabled/disabled BLC mode (on/off). + + + + + Image brightness (unit unspecified). + + + + + Color saturation of the image (unit unspecified). + + + + + Contrast of the image (unit unspecified). + + + + + Exposure mode of the device. + + + + + Focus configuration. + + + + + Infrared Cutoff Filter settings. + + + + + Sharpness of the Video image. + + + + + WDR settings. + + + + + White balance settings. + + + + + + + + + + + + + Optional element to configure Image Stabilization feature. + + + + + + + + + + + An optional parameter applied to only auto mode to adjust timing of toggling Ir cut filter. + + + + + + + + + + + + + + + + + Parameter to enable/disable Image Stabilization feature. + + + + + Optional level parameter (unit unspecified) + + + + + + + + + + + + + + + + + + + + + + + + + + + Specifies which boundaries to automatically toggle Ir cut filter following parameters are applied to. Its options shall be chosen from tt:IrCutFilterAutoBoundaryType. + + + + + Adjusts boundary exposure level for toggling Ir cut filter to on/off specified with unitless normalized value from +1.0 to -1.0. Zero is default and -1.0 is the darkest adjustment (Unitless). + + + + + Delay time of toggling Ir cut filter to on/off after crossing of the boundary exposure levels. + + + + + + + + + + + + + + + + + + + + + + + + + Type describing whether WDR mode is enabled or disabled (on/off). + + + + + Wide dynamic range mode (on/off). + + + + + Optional level parameter (unit unspecified). + + + + + + + + Type describing whether BLC mode is enabled or disabled (on/off). + + + + + Backlight compensation mode (on/off). + + + + + Optional level parameter (unit unspecified). + + + + + + + + Type describing the exposure settings. + + + + + + Exposure Mode +
    +
  • Auto – Enabled the exposure algorithm on the device.
  • +
  • Manual – Disabled exposure algorithm on the device.
  • +
+
+
+
+ + + + The exposure priority mode (low noise/framerate). + + + + + + + Rectangular exposure mask. + + + + + + + Minimum value of exposure time range allowed to be used by the algorithm. + + + + + + + Maximum value of exposure time range allowed to be used by the algorithm. + + + + + + + Minimum value of the sensor gain range that is allowed to be used by the algorithm. + + + + + + + Maximum value of the sensor gain range that is allowed to be used by the algorithm. + + + + + + + Minimum value of the iris range allowed to be used by the algorithm. + + + + + + + Maximum value of the iris range allowed to be used by the algorithm. + + + + + + + The fixed exposure time used by the image sensor (μs). + + + + + + + The fixed gain used by the image sensor (dB). + + + + + + + The fixed attenuation of input light affected by the iris (dB). 0dB maps to a fully opened iris. + + + +
+
+ + + + + + + Valid range of Backlight Compensation. + + + + + + + Valid range of Brightness. + + + + + + + Valid range of Color Saturation. + + + + + + + Valid range of Contrast. + + + + + + + Valid range of Exposure. + + + + + + + Valid range of Focus. + + + + + + + Valid range of IrCutFilterModes. + + + + + + + Valid range of Sharpness. + + + + + + + Valid range of WideDynamicRange. + + + + + + + Valid range of WhiteBalance. + + + + + + + + + + + + + + Options of parameters for Image Stabilization feature. + + + + + + + + + + + Options of parameters for adjustment of Ir cut filter auto mode. + + + + + + + + + + + + + + + + + Supported options of Image Stabilization mode parameter. + + + + + Valid range of the Image Stabilization. + + + + + + + + + + + + + + + + + + Supported options of boundary types for adjustment of Ir cut filter auto mode. The opptions shall be chosen from tt:IrCutFilterAutoBoundaryType. + + + + + Indicates whether or not boundary offset for toggling Ir cut filter is supported. + + + + + Supported range of delay time for toggling Ir cut filter. + + + + + + + + + + + + + + + + + + + + + + + + + + 'ON' or 'OFF' + + + + + + + Level range of BacklightCompensation. + + + + + + + + + + + + Exposure Mode +
    +
  • Auto – Enabled the exposure algorithm on the device.
  • +
  • Manual – Disabled exposure algorithm on the device.
  • +
+
+
+
+ + + + The exposure priority mode (low noise/framerate). + + + + + + + Valid range of the Minimum ExposureTime. + + + + + + + Valid range of the Maximum ExposureTime. + + + + + + + Valid range of the Minimum Gain. + + + + + + + Valid range of the Maximum Gain. + + + + + + + Valid range of the Minimum Iris. + + + + + + + Valid range of the Maximum Iris. + + + + + + + Valid range of the ExposureTime. + + + + + + + Valid range of the Gain. + + + + + + + Valid range of the Iris. + + + +
+
+ + + + + + + Valid ranges for the absolute control. + + + + + + + Valid ranges for the relative control. + + + + + + + Valid ranges for the continuous control. + + + + + + + + + + + + Valid ranges of the distance. + + + + + + + Valid ranges of the speed. + + + + + + + + + + + + 'AUTO' or 'MANUAL' + + + + + + + Rgain (unitless). + + + + + + + Bgain (unitless). + + + + + + + + + + + + + + + + + + + + Mode of auto fucus. +
    +
  • AUTO
  • +
  • MANUAL
  • +
+
+
+
+ + + + Parameter to set autofocus near limit (unit: meter). + + + + + Parameter to set autofocus far limit (unit: meter). + + + +
+ +
+ + + + + + + + + + + + + Mode of WhiteBalance. +
    +
  • AUTO
  • +
  • MANUAL
  • +
+
+
+
+ + + +
+
+ + + + + + + + + + + + + Mode of Auto Focus. +
    +
  • AUTO
  • +
  • MANUAL
  • +
+
+
+
+ + + + Valid range of DefaultSpeed. + + + + + + + Valid range of NearLimit. + + + + + + + Valid range of FarLimit. + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Token value pairs that triggered this message. Typically only one item is present. + + + + + + + + + + + + + + + + + + + + + + List of parameters according to the corresponding ItemListDescription. + Each item in the list shall have a unique name. + + + + + + Value name pair as defined by the corresponding description. + + + + + Item name. + + + + + Item value. The type is defined in the corresponding description. + + + + + + + Complex value structure. + + + + + + XML tree contiaing the element value as defined in the corresponding description. + + + + + + Item name. + + + + + + + + + + + + + + + + + + + + + + Set of tokens producing this message. The list may only contain SimpleItemDescription items. + The set of tokens identify the component within the WS-Endpoint, which is responsible for the producing the message.
+ For analytics events the token set shall include the VideoSourceConfigurationToken, the VideoAnalyticsConfigurationToken + and the name of the analytics module or rule. +
+
+
+ + + Describes optional message payload parameters that may be used as key. E.g. object IDs of tracked objects are conveyed as key. + + + + + Describes the payload of the message. + + + +
+ + + Must be set to true when the described Message relates to a property. An alternative term of "property" is a "state" in contrast to a pure event, which contains relevant information for only a single point in time.
Default is false.
+
+
+ +
+ + + + + + + + + + + Describes a list of items. Each item in the list shall have a unique name. + The list is designed as linear structure without optional or unbounded elements. + Use ElementItems only when complex structures are inevitable. + + + + + + Description of a simple item. The type must be of cathegory simpleType (xs:string, xs:integer, xs:float, ...). + + + + + Item name. Must be unique within a list. + + + + + + + + + Description of a complex type. The Type must reference a defined type. + + + + + + Item name. Must be unique within a list. + + + + + The type of the item. The Type must reference a defined type. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Object Class Type + + + + + A likelihood/probability that the corresponding object belongs to this class. The sum of the likelihoods shall NOT exceed 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Number of columns of the cell grid (x dimension) + + + + + Number of rows of the cell grid (y dimension) + + + + + A “1” denotes a cell where motion is detected and a “0” an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + List of configuration parameters as defined in the correspding description. + + + + + + Name of the configuration. + + + + + Type of the configuration represented by a unique QName. The Type characterizes a ConfigDescription defining the Parameters. + + + + + + + + + + List describing the configuration parameters. The names of the parameters must be unique. If possible SimpleItems + should be used to transport the information to ease parsing of dynamically defined messages by a client + application. + + + + + + + The analytics modules and rule engine produce Events, which must be listed within the Analytics Module Description. In order to do so + the structure of the Message is defined and consists of three groups: Source, Key, and Data. It is recommended to use SimpleItemDescriptions wherever applicable. + The name of all Items must be unique within all Items contained in any group of this Message. + Depending on the component multiple parameters or none may be needed to identify the component uniquely. + + + + + + + + + + The ParentTopic labels the message (e.g. "nn:RuleEngine/LineCrossing"). The real message can extend the ParentTopic + by for example the name of the instaniated rule (e.g. "nn:RuleEngine/LineCrossing/corssMyFirstLine"). + Even without knowing the complete topic name, the subscriber will be able to distiguish the + messages produced by different rule instances of the same type via the Source fields of the message. + There the name of the rule instance, which produced the message, must be listed. + + + + + + + + + + + + + XML Type of the Configuration (e.g. "tt::LineDetector"). + + + + + + + + + + + + + + + + Lists the location of all schemas that are referenced in the rules. + + + + + List of rules supported by the Video Analytics configuration.. + + + + + + + + + + + + + + + + + + It optionally contains a list of URLs that provide the location of schema files. + These schema files describe the types and elements used in the analytics module descriptions. + If the analytics module descriptions reference types or elements of the ONVIF schema file, + the ONVIF schema file MUST be explicitly listed. + + + + + + + + + + + + + + + + + + + Contains Polygon configuration for rule parameters + + + + + + + + + + + + Contains array of Polyline + + + + + + + + + + + + + + + + + + Contains PolylineArray configuration data + + + + + + + + + + + + Motion Expression data structure contains motion expression which is based on Scene Descriptor schema with XPATH syntax. The Type argument could allow introduction of different dialects + + + + + + + + + + + + + Contains Rule MotionExpression configuration + + + + + + + + + + + + Mapping of the cell grid to the Video frame. The cell grid is starting from the upper left corner and x dimension is going from left to right and the y dimension from up to down. + + + + + + + Number of columns of the cell grid (x dimension) + + + + + Number of rows of the cell grid (y dimension) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configuration of the streaming and coding settings of a Video window. + + + + + Optional name of the pane configuration. + + + + + If the device has audio outputs, this element contains a pointer to the audio output that is associated with the pane. A client +can retrieve the available audio outputs of a device using the GetAudioOutputs command of the DeviceIO service. + + + + + If the device has audio sources, this element contains a pointer to the audio source that is associated with this pane. +The audio connection from a decoder device to the NVT is established using the backchannel mechanism. A client can retrieve the available audio sources of a device using the GetAudioSources command of the +DeviceIO service. + + + + + The configuration of the audio encoder including codec, bitrate +and sample rate. + + + + + A pointer to a Receiver that has the necessary information to receive + data from a Transmitter. This Receiver can be connected and the network video decoder displays the received data on the specified outputs. A client can retrieve the available Receivers using the + GetReceivers command of the Receiver Service. + + + + + A unique identifier in the display device. + + + + + + + + + + A pane layout describes one Video window of a display. It links a pane configuration to a region of the screen. + + + + + Reference to the configuration of the streaming and coding parameters. + + + + + Describes the location and size of the area on the monitor. The area coordinate values are espressed in normalized units [-1.0, 1.0]. + + + + + + + + + + A layout describes a set of Video windows that are displayed simultaniously on a display. + + + + + List of panes assembling the display layout. + + + + + + + + + + + + + + + + This type contains the Audio and Video coding capabilities of a display service. + + + + + If the device supports audio encoding this section describes the supported codecs and their configuration. + + + + + If the device supports audio decoding this section describes the supported codecs and their settings. + + + + + This section describes the supported video codesc and their configuration. + + + + + + + + + + The options supported for a display layout. + + + + + Lists the possible Pane Layouts of the Video Output + + + + + + + + + + + + + + + + Description of a pane layout describing a complete display layout. + + + + + List of areas assembling a layout. Coordinate values are in the range [-1.0, 1.0]. + + + + + + + + + + + + + + + + + + + + + + Description of a receiver, including its token and configuration. + + + + + + Unique identifier of the receiver. + + + + + Describes the configuration of the receiver. + + + + + + + + + + + Describes the configuration of a receiver. + + + + + + The following connection modes are defined: + + + + + Details of the URI to which the receiver should connect. + + + + + Stream connection parameters. + + + + + + + + + + + Specifies a receiver connection mode. + + + + + + The receiver connects on demand, as required by consumers of the media streams. + + + + + The receiver attempts to maintain a persistent connection to the configured endpoint. + + + + + The receiver does not attempt to connect. + + + + + This case should never happen. + + + + + + + + + Specifies the current connection state of the receiver. + + + + + + The receiver is not connected. + + + + + The receiver is attempting to connect. + + + + + The receiver is connected. + + + + + This case should never happen. + + + + + + + + + Contains information about a receiver's current state. + + + + + + The connection state of the receiver may have one of the following states: + + + + + Indicates whether or not the receiver was created automatically. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The earliest point in time where there is recorded data on the device. + + + + + The most recent point in time where there is recorded data on the device. + + + + + The device contains this many recordings. + + + + + + + + + + A structure for defining a limited scope when searching in recorded data. + + + + + A list of sources that are included in the scope. If this list is included, only data from one of these sources shall be searched. + + + + + A list of recordings that are included in the scope. If this list is included, only data from one of these recordings shall be searched. + + + + + An xpath expression used to specify what recordings to search. Only those recordings with an RecordingInformation structure that matches the filter shall be searched. + + + + + Extension point + + + + + + + + + + + + + + + + + + + The lower boundary of the PTZ volume to look for. + + + + + The upper boundary of the PTZ volume to look for. + + + + + If true, search for when entering the specified PTZ volume. + + + + + + + + + + + + + + + + + + + + + + + + The state of the search when the result is returned. Indicates if there can be more results, or if the search is completed. + + + + + A RecordingInformation structure for each found recording matching the search. + + + + + + + + + + The state of the search when the result is returned. Indicates if there can be more results, or if the search is completed. + + + + + A FindEventResult structure for each found event matching the search. + + + + + + + + + + The recording where this event was found. Empty string if no recording is associated with this event. + + + + + A reference to the track where this event was found. Empty string if no track is associated with this event. + + + + + The time when the event occured. + + + + + + The description of the event. + + + + + If true, indicates that the event is a virtual event generated for this particular search session to give the state of a property at the start time of the search. + + + + + + + + + + + + The state of the search when the result is returned. Indicates if there can be more results, or if the search is completed. + + + + + A FindPTZPositionResult structure for each found PTZ position matching the search. + + + + + + + + + + A reference to the recording containing the PTZ position. + + + + + A reference to the metadata track containing the PTZ position. + + + + + The time when the PTZ position was valid. + + + + + The PTZ position. + + + + + + + + + + + + The state of the search when the result is returned. Indicates if there can be more results, or if the search is completed. + + + + + A FindMetadataResult structure for each found set of Metadata matching the search. + + + + + + + + + + A reference to the recording containing the metadata. + + + + + A reference to the metadata track containing the matching metadata. + + + + + The point in time when the matching metadata occurs in the metadata track. + + + + + + + + + + + + The search is queued and not yet started. + + + + + The search is underway and not yet completed. + + + + + The search has been completed and no new results will be found. + + + + + The state of the search is unknown. (This is not a valid response from GetSearchState.) + + + + + + + + + + + + + + + + Information about the source of the recording. This gives a description of where the data in the recording comes from. Since a single + recording is intended to record related material, there is just one source. It is indicates the physical location or the + major data source for the recording. Currently the recordingconfiguration cannot describe each individual data source. + + + + + + + + + Basic information about the track. Note that a track may represent a single contiguous time span or consist of multiple slices. + + + + + + + + + + + + A set of informative desciptions of a data source. The Search searvice allows a client to filter on recordings based on information in this structure. + + + + + + + Identifier for the source chosen by the client that creates the structure. + This identifier is opaque to the device. Clients may use any type of URI for this field. A device shall support at least 128 characters. + + + + + Informative user readable name of the source, e.g. "Camera23". A device shall support at least 20 characters. + + + + + Informative description of the physical location of the source, e.g. the coordinates on a map. + + + + + Informative description of the source. + + + + + URI provided by the service supplying data to be recorded. A device shall support at least 128 characters. + + + + + + + + + + + + + + + + + This case should never happen. + + + + + + + + + + + Type of the track: "Video", "Audio" or "Metadata". + The track shall only be able to hold data of that type. + + + + + Informative description of the contents of the track. + + + + + The start date and time of the oldest recorded data in the track. + + + + + The stop date and time of the newest recorded data in the track. + + + + + + + + + + + + + + + Placeholder for future extension. + + + + + + + + A set of media attributes valid for a recording at a point in time or for a time interval. + + + + + A reference to the recording that has these attributes. + + + + + A set of attributes for each track. + + + + + The attributes are valid from this point in time in the recording. + + + + + The attributes are valid until this point in time in the recording. Can be equal to 'From' to indicate that the attributes are only known to be valid for this particular point in time. + + + + + + + + + + + + The basic information about the track. Note that a track may represent a single contiguous time span or consist of multiple slices. + + + + + If the track is a video track, exactly one of this structure shall be present and contain the video attributes. + + + + + If the track is an audio track, exactly one of this structure shall be present and contain the audio attributes. + + + + + If the track is an metadata track, exactly one of this structure shall be present and contain the metadata attributes. + + + + + + + + + + + + + + + + + + + + + + Average bitrate in kbps. + + + + + The width of the video in pixels. + + + + + The height of the video in pixels. + + + + + Used video codec, either Jpeg, H.264 or Mpeg4 + + + + + Average framerate in frames per second. + + + + + + + + + + + + The bitrate in kbps. + + + + + Audio codec used for encoding the audio (either G.711, G.726 or AAC) + + + + + The sample rate in kHz. + + + + + + + + + + + + Indicates that there can be PTZ data in the metadata track in the specified time interval. + + + + + Indicates that there can be analytics data in the metadata track in the specified time interval. + + + + + Indicates that there can be notifications in the metadata track in the specified time interval. + + + + + + + List of all PTZ spaces active for recording. Note that events are only recorded on position changes and the actual point of recording may not necessarily contain an event of the specified type. + + + + + + + + + + + + + + + + + Information about the source of the recording. + + + + + Informative description of the source. + + + + + Sspecifies the maximum time that data in any track within the + recording shall be stored. The device shall delete any data older than the maximum retention + time. Such data shall not be accessible anymore. If the MaximumRetentionPeriod is set to 0, + the device shall not limit the retention time of stored data, except by resource constraints. + Whatever the value of MaximumRetentionTime, the device may automatically delete + recordings to free up storage space for new recordings. + + + + + + + + + + + + Type of the track. It shall be equal to the strings “Video”, + “Audio” or “Metadata”. The track shall only be able to hold data of that type. + + + + + Informative description of the track. + + + + + + + + + + + + Token of the recording. + + + + + Configuration of the recording. + + + + + List of tracks. + + + + + + + + + + + + Configuration of a track. + + + + + + + + + + + Token of the track. + + + + + Configuration of the track. + + + + + + + + + + + + Identifies the recording to which this job shall store the received data. + + + + + The mode of the job. If it is idle, nothing shall happen. If it is active, the device shall try + to obtain data from the receivers. A client shall use GetRecordingJobState to determine if data transfer is really taking place.
+ The only valid values for Mode shall be “Idle” and “Active”.
+
+
+ + + This shall be a non-negative number. If there are multiple recording jobs that store data to + the same track, the device will only store the data for the recording job with the highest + priority. The priority is specified per recording job, but the device shall determine the priority + of each track individually. If there are two recording jobs with the same priority, the device + shall record the data corresponding to the recording job that was activated the latest. + + + + + Source of the recording. + + + +
+ +
+ + + + + + + + + + + + + + + + This field shall be a reference to the source of the data. The type of the source + is determined by the attribute Type in the SourceToken structure. If Type is + http://www.onvif.org/ver10/schema/Receiver, the token is a ReceiverReference. In this case + the device shall receive the data over the network. If Type is + http://www.onvif.org/ver10/schema/Profile, the token identifies a media profile, instructing the + device to obtain data from a profile that exists on the local device. + + + + + If this field is TRUE, and if the SourceToken is omitted, the device + shall create a receiver object (through the receiver service) and assign the + ReceiverReference to the SourceToken field. When retrieving the RecordingJobConfiguration + from the device, the AutoCreateReceiver field shall never be present. + + + + + List of tracks associated with the recording. + + + + + + + + + + + + + + + + + + If the received RTSP stream contains multiple tracks of the same type, the + SourceTag differentiates between those Tracks. This field can be ignored in case of recording a local source. + + + + + The destination is the tracktoken of the track to which the device shall store the + received data. + + + + + + + + + + + + Identification of the recording that the recording job records to. + + + + + Holds the aggregated state over the whole RecordingJobInformation structure. + + + + + Identifies the data source of the recording job. + + + + + + + + + + + + + + + + + + + + + + Identifies the data source of the recording job. + + + + + Holds the aggregated state over all substructures of RecordingJobStateSource. + + + + + List of track items. + + + + + + + + + + + + + + + + + + + Identifies the track of the data source that provides the data. + + + + + Indicates the destination track. + + + + + Optionally holds an implementation defined string value that describes the error. + The string should be in the English language. + + + + + Provides the job state of the track. The valid + values of state shall be “Idle”, “Active” and “Error”. If state equals “Error”, the Error field may be filled in with an implementation defined value. + + + + + + + + + + + + + + + + + + + + + + + + + + Configuration parameters for the replay service. + + + + + + The RTSP session timeout. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Token of the analytics engine (AnalyticsEngine) being controlled. + + + + + Token of the analytics engine configuration (VideoAnalyticsConfiguration) in effect. + + + + + Tokens of the input (AnalyticsEngineInput) configuration applied. + + + + + Tokens of the receiver providing media input data. The order of ReceiverToken shall exactly match the order of InputToken. + + + + + + + + + + + + + + + + + + + This case should never happen. + + + + + + + + + + Token of the control object whose status is requested. + + + + + + + + + + + + + + + + + + + + + + + + + + Action Engine Event Payload data structure contains the information about the ONVIF command invocations. Since this event could be generated by other or proprietary actions, the command invocation specific fields are defined as optional and additional extension mechanism is provided for future or additional action definitions. + + + + + Request Message + + + + + Response Message + + + + + Fault Message + + + + + + + + + + + + + + + + + + + + + + + AudioClassType acceptable values are; + gun_shot, scream, glass_breaking, tire_screech + + + + + + + + + + Indicates audio class label + + + + + A likelihood/probability that the corresponding audio event belongs to this class. The sum of the likelihoods shall NOT exceed 1 + + + + + + + + + + + + Array of audio class label and class probability + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + For OSD position type, following are the pre-defined:
  • UpperLeft
  • +
  • UpperRight
  • +
  • LowerLeft
  • +
  • LowerRight
  • +
  • Custom
+
+
+
+ + +
+ +
+ + + + + + + + + + + The value range of "Transparent" could be defined by vendors only should follow this rule: the minimum value means non-transparent and the maximum value maens fully transparent. + + + + + + + + + + + + + + The following OSD Text Type are defined:
    +
  • Plain - The Plain type means the OSD is shown as a text string which defined in the "PlainText" item.
  • +
  • Date - The Date type means the OSD is shown as a date, format of which should be present in the "DateFormat" item.
  • +
  • Time - The Time type means the OSD is shown as a time, format of which should be present in the "TimeFormat" item.
  • +
  • DateAndTime - The DateAndTime type means the OSD is shown as date and time, format of which should be present in the "DateFormat" and the "TimeFormat" item.
  • +
+
+
+
+ + + + List of supported OSD date formats. This element shall be present when the value of Type field has Date or DateAndTime. The following DateFormat are defined:
    +
  • M/d/yyyy - e.g. 3/6/2013
  • +
  • MM/dd/yyyy - e.g. 03/06/2013
  • +
  • dd/MM/yyyy - e.g. 06/03/2013
  • +
  • yyyy/MM/dd - e.g. 2013/03/06
  • +
  • yyyy-MM-dd - e.g. 2013-06-03
  • +
  • dddd, MMMM dd, yyyy - e.g. Wednesday, March 06, 2013
  • +
  • MMMM dd, yyyy - e.g. March 06, 2013
  • +
  • dd MMMM, yyyy - e.g. 06 March, 2013
  • +
+
+
+
+ + + + List of supported OSD time formats. This element shall be present when the value of Type field has Time or DateAndTime. The following TimeFormat are defined:
    +
  • h:mm:ss tt - e.g. 2:14:21 PM
  • +
  • hh:mm:ss tt - e.g. 02:14:21 PM
  • +
  • H:mm:ss - e.g. 14:14:21
  • +
  • HH:mm:ss - e.g. 14:14:21
  • +
+
+
+
+ + + Font size of the text in pt. + + + + + Font color of the text. + + + + + Background color of the text. + + + + + The content of text to be displayed. + + + +
+ +
+ + + + + + + + + + + + + The URI of the image which to be displayed. + + + + + + + + + + + + + + + + + + + + + + + + + + + Describe the option of the color supported. Either list each color or define the range of color value. The following values are acceptable for Colourspace attribute.
  • http://www.onvif.org/ver10/colorspace/YCbCr - YCbCr colourspace
  • +
  • http://www.onvif.org/ver10/colorspace/CIELUV - CIE LUV
  • +
  • http://www.onvif.org/ver10/colorspace/CIELAB - CIE 1976 (L*a*b*)
  • +
  • http://www.onvif.org/ver10/colorspace/HSV - HSV colourspace
+
+
+ + + + + List the supported color. + + + + + Define the rang of color supported. + + + + + +
+ + + + Describe the option of the color and its transparency. + + + + + Optional list of supported colors. + + + + + Range of the transparent level. Larger means more tranparent. + + + + + + + + + + + + + + + + + + + List of supported OSD text type. When a device indicates the supported number relating to Text type in MaximumNumberOfOSDs, the type shall be presented. + + + + + Range of the font size value. + + + + + List of supported date format. + + + + + List of supported time format. + + + + + List of supported font color. + + + + + List of supported background color. + + + + + + + + + + + + + + + + + + + List of avaiable uris of image. + + + + + + + + + + + + + + + + + + + + + Reference to the video source configuration. + + + + + Type of OSD. + + + + + Position configuration of OSD. + + + + + Text configuration of OSD. It shall be present when the value of Type field is Text. + + + + + Image configuration of OSD. It shall be present when the value of Type field is Image + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The maximum number of OSD configurations supported for the specificate video source configuration. If a device limits the number of instances by OSDType, it should indicate the supported number via the related attribute. + + + + + List supported type of OSD configuration. When a device indicates the supported number for each types in MaximumNumberOfOSDs, related type shall be presented. A device shall return Option element relating to listed type. + + + + + List available OSD position type. Following are the pre-defined:
  • UpperLeft
  • +
  • UpperRight
  • +
  • LowerLeft
  • +
  • LowerRight
  • +
  • Custom
+
+
+
+ + + Option of the OSD text configuration. This element shall be returned if the device is signaling the support for Text. + + + + + Option of the OSD image configuration. This element shall be returned if the device is signaling the support for Image. + + + +
+ +
+ + + + + + + + + + +
diff --git a/onvif/wsdl/onvif.xsd b/onvif/wsdl/onvif.xsd new file mode 100644 index 000000000..252204e2f --- /dev/null +++ b/onvif/wsdl/onvif.xsd @@ -0,0 +1,8559 @@ + + + + + + + + + + + + + + Base class for physical entities like inputs and outputs. + + + + Unique identifier referencing the physical entity. + + + + + + + Unique identifier for a physical or logical resource. + Tokens should be assigned such that they are unique within a device. Tokens must be at least unique within its class. + Length up to 64 characters. + + + + + + + + + User readable name. Length up to 64 characters. + + + + + + + + + Rectangle defined by lower left corner position and size. Units are pixel. + + + + + + + + + + Range of a rectangle. The rectangle itself is defined by lower left corner position and size. Units are pixel. + + + + + Range of X-axis. + + + + + Range of Y-axis. + + + + + Range of width. + + + + + Range of height. + + + + + + + + Range of values greater equal Min value and less equal Max value. + + + + + + + + + + Range of values greater equal Min value and less equal Max value. + + + + + + + + + + Range of duration greater equal Min duration and less equal Max duration. + + + + + + + + + + List of values. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Representation of a physical video input. + + + + + + + Frame rate in frames per second. + + + + + Horizontal and vertical resolution + + + + + Optional configuration of the image sensor. + + + + + + + + + + + + + + + Optional configuration of the image sensor. To be used if imaging service 2.00 is supported. + + + + + + + + + + + + + + + Representation of a physical audio input. + + + + + + + number of available audio channels. (1: mono, 2: stereo) + + + + + + + + + + + + + A media profile consists of a set of media configurations. Media profiles are used by a client + to configure properties of a media stream from an NVT.
+ An NVT shall provide at least one media profile at boot. An NVT should provide “ready to use” + profiles for the most common media configurations that the device offers.
+ A profile consists of a set of interconnected configuration entities. Configurations are provided + by the NVT and can be either static or created dynamically by the NVT. For example, the + dynamic configurations can be created by the NVT depending on current available encoding + resources. +
+
+ + + + User readable name of the profile. + + + + + Optional configuration of the Video input. + + + + + Optional configuration of the Audio input. + + + + + Optional configuration of the Video encoder. + + + + + Optional configuration of the Audio encoder. + + + + + Optional configuration of the video analytics module and rule engine. + + + + + Optional configuration of the pan tilt zoom unit. + + + + + Optional configuration of the metadata stream. + + + + + Extensions defined in ONVIF 2.0 + + + + + + Unique identifier of the profile. + + + + + A value of true signals that the profile cannot be deleted. Default is false. + + + +
+ + + + + + + Optional configuration of the Audio output. + + + + + Optional configuration of the Audio decoder. + + + + + + + + + + + + + + + + + + + + + + + + + + Base type defining the common properties of a configuration. + + + + + User readable name. Length up to 64 characters. + + + + + Number of internal references currently using this configuration.
This parameter is read-only and cannot be changed by a set request.
For example the value increases if the configuration is added to a media profile or attached to a PaneConfiguration.
+
+
+
+ + + Token that uniquely refernces this configuration. Length up to 64 characters. + + +
+ + + + + + + + + + Reference to the physical input. + + + + + Rectangle specifying the Video capturing area. The capturing area shall not be larger than the whole Video source area. + + + + + + + + + + + + + + + Optional element to configure rotation of captured image. + + + + + + + + + + + + + + + + + Parameter to enable/disable Rotation feature. + + + + + Optional parameter to configure how much degree of clockwise rotation of image for On mode. Omitting this parameter for On mode means 180 degree rotation. + + + + + + + + + + + + + + + + + + + + + + + + + + Supported range for the capturing area. + + + + + List of physical inputs. + + + + + + + + + + + + + Options of parameters for Rotation feature. + + + + + + + + + + + + + + + + + Supported options of Rotate mode parameter. + + + + + List of supported degree value for rotation. + + + + + + + + + + + + + + + + + + + + + + Used video codec, either Jpeg, H.264 or Mpeg4 + + + + + Configured video resolution + + + + + Relative value for the video quantizers and the quality of the video. A high value within supported quality range means higher quality + + + + + Optional element to configure rate control related parameters. + + + + + Optional element to configure Mpeg4 related parameters. + + + + + Optional element to configure H.264 related parameters. + + + + + Defines the multicast settings that could be used for video streaming. + + + + + The rtsp session timeout for the related video stream + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Number of the columns of the Video image. + + + + + Number of the lines of the Video image. + + + + + + + + + + Maximum output framerate in fps. If an EncodingInterval is provided the resulting encoded framerate will be reduced by the given factor. + + + + + Interval at which images are encoded and transmitted. (A value of 1 means that every frame is encoded, a value of 2 means that every 2nd frame is encoded ...) + + + + + the maximum output bitrate in kbps + + + + + + + + + + Determines the interval in which the I-Frames will be coded. An entry of 1 indicates I-Frames are continuously generated. An entry of 2 indicates that every 2nd image is an I-Frame, and 3 only every 3rd frame, etc. The frames in between are coded as P or B Frames. + + + + + the Mpeg4 profile, either simple profile (SP) or advanced simple profile (ASP) + + + + + + + + + + Group of Video frames length. Determines typically the interval in which the I-Frames will be coded. An entry of 1 indicates I-Frames are continuously generated. An entry of 2 indicates that every 2nd image is an I-Frame, and 3 only every 3rd frame, etc. The frames in between are coded as P or B Frames. + + + + + the H.264 profile, either baseline, main, extended or high + + + + + + + + + + Range of the quality values. A high value means higher quality. + + + + + Optional JPEG encoder settings ranges (See also Extension element). + + + + + Optional MPEG-4 encoder settings ranges (See also Extension element). + + + + + Optional H.264 encoder settings ranges (See also Extension element). + + + + + + + + + + + + + Optional JPEG encoder settings ranges. + + + + + Optional MPEG-4 encoder settings ranges. + + + + + Optional H.264 encoder settings ranges. + + + + + + + + + + + + + + + + + List of supported image sizes. + + + + + Supported frame rate in fps (frames per second). + + + + + Supported encoding interval range. The encoding interval corresponds to the number of frames devided by the encoded frames. An encoding interval value of "1" means that all frames are encoded. + + + + + + + + + + + + Supported range of encoded bitrate in kbps. + + + + + + + + + + + + + + List of supported image sizes. + + + + + Supported group of Video frames length. This value typically corresponds to the I-Frame distance. + + + + + Supported frame rate in fps (frames per second). + + + + + Supported encoding interval range. The encoding interval corresponds to the number of frames devided by the encoded frames. An encoding interval value of "1" means that all frames are encoded. + + + + + List of supported MPEG-4 profiles. + + + + + + + + + + + + Supported range of encoded bitrate in kbps. + + + + + + + + + + + + + + List of supported image sizes. + + + + + Supported group of Video frames length. This value typically corresponds to the I-Frame distance. + + + + + Supported frame rate in fps (frames per second). + + + + + Supported encoding interval range. The encoding interval corresponds to the number of frames devided by the encoded frames. An encoding interval value of "1" means that all frames are encoded. + + + + + List of supported H.264 profiles. + + + + + + + + + + + + Supported range of encoded bitrate in kbps. + + + + + + + + + + + + + + + + + + Token of the Audio Source the configuration applies to + + + + + + + + + + + + + + Tokens of the audio source the configuration can be used for. + + + + + + + + + + + + + + + + + + + + + + Audio codec used for encoding the audio input (either G.711, G.726 or AAC) + + + + + The output bitrate in kbps. + + + + + The output sample rate in kHz. + + + + + Defines the multicast settings that could be used for video streaming. + + + + + The rtsp session timeout for the related audio stream + + + + + + + + + + + + + + + + + + + + + + list of supported AudioEncoderConfigurations + + + + + + + + + + + The enoding used for audio data (either G.711, G.726 or AAC) + + + + + List of supported bitrates in kbps for the specified Encoding + + + + + List of supported Sample Rates in kHz for the specified Encoding + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + optional element to configure which PTZ related data is to include in the metadata stream + + + + + Optional element to configure the streaming of events. A client might be interested in receiving all, + none or some of the events produced by the device:
    +
  • To get all events: Include the Events element but do not include a filter.
  • +
  • To get no events: Do not include the Events element.
  • +
  • To get only some events: Include the Events element and include a filter in the element.
  • +
+
+
+
+ + + Defines whether the streamed metadata will include metadata from the analytics engines (video, cell motion, audio etc.) + + + + + Defines the multicast settings that could be used for video streaming. + + + + + The rtsp session timeout for the related audio stream + + + + + +
+ +
+
+
+ + + + + + + + + + + + True if the metadata stream shall contain the PTZ status (IDLE, MOVING or UNKNOWN) + + + + + True if the metadata stream shall contain the PTZ position + + + + + + + + + Subcription handling in the same way as base notification subscription. + + + + + + + + + + + + + + + + + + + + + + + + + + + + True if the device is able to stream pan or tilt status information. + + + + + True if the device is able to stream zoom status inforamtion. + + + + + + True if the device is able to stream the pan or tilt position. + + + + + True if the device is able to stream zoom position information. + + + + + + + + + + + + + + + + + + Representation of a physical video outputs. + + + + + + + + Resolution of the display in Pixel. + + + + + Refresh rate of the display in Hertz. + + + + + Aspect ratio of the display as physical extent of width divided by height. + + + + + + + + + + + + + + + + + + + + + + + + Token of the Video Output the configuration applies to + + + + + + + + + + + + + + + + + + + + + + + + + If the device is able to decode Jpeg streams this element describes the supported codecs and configurations + + + + + If the device is able to decode H.264 streams this element describes the supported codecs and configurations + + + + + If the device is able to decode Mpeg4 streams this element describes the supported codecs and configurations + + + + + + + + + + + + List of supported H.264 Video Resolutions + + + + + List of supported H264 Profiles (either baseline, main, extended or high) + + + + + Supported H.264 bitrate range in kbps + + + + + Supported H.264 framerate range in fps + + + + + + + + + + + + List of supported Jpeg Video Resolutions + + + + + Supported Jpeg bitrate range in kbps + + + + + Supported Jpeg framerate range in fps + + + + + + + + + + + + List of supported Mpeg4 Video Resolutions + + + + + List of supported Mpeg4 Profiles (either SP or ASP) + + + + + Supported Mpeg4 bitrate range in kbps + + + + + Supported Mpeg4 framerate range in fps + + + + + + + + + + + + + + + + + + Representation of a physical audio outputs. + + + + + + + + + + + + + + + + + + + + Token of the phsycial Audio output. + + + + + + An audio channel MAY support different types of audio transmission. While for full duplex + operation no special handling is required, in half duplex operation the transmission direction + needs to be switched. + The optional SendPrimacy parameter inside the AudioOutputConfiguration indicates which + direction is currently active. An NVC can switch between different modes by setting the + AudioOutputConfiguration.
+ The following modes for the Send-Primacy are defined:
    +
  • www.onvif.org/ver20/HalfDuplex/Server + The server is allowed to send audio data to the client. The client shall not send + audio data via the backchannel to the NVT in this mode.
  • +
  • www.onvif.org/ver20/HalfDuplex/Client + The client is allowed to send audio data via the backchannel to the server. The + NVT shall not send audio data to the client in this mode.
  • +
  • www.onvif.org/ver20/HalfDuplex/Auto + It is up to the device how to deal with sending and receiving audio data.
  • +
+ Acoustic echo cancellation is out of ONVIF scope.
+
+
+ + + Volume setting of the output. The applicable range is defined via the option AudioOutputOptions.OutputLevelRange. + + + +
+ +
+
+
+ + + + + + + + Tokens of the physical Audio outputs (typically one). + + + + + + An audio channel MAY support different types of audio transmission. While for full duplex + operation no special handling is required, in half duplex operation the transmission direction + needs to be switched. + The optional SendPrimacy parameter inside the AudioOutputConfiguration indicates which + direction is currently active. An NVC can switch between different modes by setting the + AudioOutputConfiguration.
+ The following modes for the Send-Primacy are defined:
    +
  • www.onvif.org/ver20/HalfDuplex/Server + The server is allowed to send audio data to the client. The client shall not send + audio data via the backchannel to the NVT in this mode.
  • +
  • www.onvif.org/ver20/HalfDuplex/Client + The client is allowed to send audio data via the backchannel to the server. The + NVT shall not send audio data to the client in this mode.
  • +
  • www.onvif.org/ver20/HalfDuplex/Auto + It is up to the device how to deal with sending and receiving audio data.
  • +
+ Acoustic echo cancellation is out of ONVIF scope.
+
+
+ + + Minimum and maximum level range supported for this Output. + + + +
+ +
+ + + + + + The Audio Decoder Configuration does not contain any that parameter to configure the +decoding .A decoder shall decode every data it receives (according to its capabilities). + + + + + + + + + + + + + + + + + + If the device is able to decode AAC encoded audio this section describes the supported configurations + + + + + If the device is able to decode G711 encoded audio this section describes the supported configurations + + + + + If the device is able to decode G726 encoded audio this section describes the supported configurations + + + + + + + + + + + + List of supported bitrates in kbps + + + + + List of supported sample rates in kHz + + + + + + + + + + + + List of supported bitrates in kbps + + + + + List of supported sample rates in kHz + + + + + + + + + + + + List of supported bitrates in kbps + + + + + List of supported sample rates in kHz + + + + + + + + + + + + + + + + + + + + The multicast address (if this address is set to 0 no multicast streaming is enaled) + + + + + The RTP mutlicast destination port. A device may support RTCP. In this case the port value shall be even to allow the corresponding RTCP stream to be mapped to the next higher (odd) destination port number as defined in the RTSP specification. + + + + + In case of IPv6 the TTL value is assumed as the hop limit. Note that for IPV6 and administratively scoped IPv4 multicast the primary use for hop limit / TTL is to prevent packets from (endlessly) circulating and not limiting scope. In these cases the address contains the scope. + + + + + Read only property signalling that streaming is persistant. Use the methods StartMulticastStreaming and StopMulticastStreaming to switch its state. + + + + + + + + + + + + Defines if a multicast or unicast stream is requested + + + + + + + + + + + + + + + + + + + + Defines the network protocol for streaming, either UDP=RTP/UDP, RTSP=RTP/RTSP/TCP or HTTP=RTP/RTSP/HTTP/TCP + + + + + Optional element to describe further tunnel options. This element is normally not needed + + + + + + + + + + + + + + + + + + + Stable Uri to be used for requesting the media stream + + + + + Indicates if the Uri is only valid until the connection is established. The value shall be set to "false". + + + + + Indicates if the Uri is invalid after a reboot of the device. The value shall be set to "false". + + + + + Duration how long the Uri is valid. This parameter shall be set to PT0S to indicate that this stream URI is indefinitely valid even if the profile changes + + + + + + + + + + + + + + + + + + + + + + + + + Indicates if the scope is fixed or configurable. + + + + + Scope item URI. + + + + + + + + + + + + + + + + + + + + + + + + + Indicates whether or not an interface is enabled. + + + + + Network interface information + + + + + Link configuration. + + + + + IPv4 network interface configuration. + + + + + IPv6 network interface configuration. + + + + + + + + + + + + + + + + Extension point prepared for future 802.3 configuration. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configured link settings. + + + + + Current active link settings. + + + + + Integer indicating interface type, for example: 6 is ethernet. + + + + + + + + + + Auto negotiation on/off. + + + + + Speed. + + + + + Duplex type, Half or Full. + + + + + + + + + + + + + + + + + For valid numbers, please refer to http://www.iana.org/assignments/ianaiftype-mib. + + + + + + + + + + Network interface name, for example eth0. + + + + + Network interface MAC address. + + + + + Maximum transmission unit. + + + + + + + + + + Indicates whether or not IPv6 is enabled. + + + + + IPv6 configuration. + + + + + + + + + + Indicates whether or not IPv4 is enabled. + + + + + IPv4 configuration. + + + + + + + + + + List of manually added IPv4 addresses. + + + + + Link local address. + + + + + IPv4 address configured by using DHCP. + + + + + Indicates whether or not DHCP is used. + + + + + + + + + + + + Indicates whether router advertisment is used. + + + + + DHCP configuration. + + + + + List of manually entered IPv6 addresses. + + + + + List of link local IPv6 addresses. + + + + + List of IPv6 addresses configured by using DHCP. + + + + + List of IPv6 addresses configured by using router advertisment. + + + + + + + + + + + + + + + + + + + + + + + + + + + Network protocol type string. + + + + + Indicates if the protocol is enabled or not. + + + + + The port that is used by the protocol. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Network host type: IPv4, IPv6 or DNS. + + + + + IPv4 address. + + + + + IPv6 address. + + + + + DNS name. + + + + + + + + + + + + + + + + + + Indicates if the address is an IPv4 or IPv6 address. + + + + + IPv4 address. + + + + + IPv6 address + + + + + + + + + + IPv4 address + + + + + Prefix/submask length + + + + + + + + + + + + + + IPv6 address + + + + + Prefix/submask length + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Indicates whether the hostname is obtained from DHCP or not. + + + + + Indicates the hostname. + + + + + + + + + + + + + + + + + + Indicates whether or not DNS information is retrieved from DHCP. + + + + + Search domain. + + + + + List of DNS addresses received from DHCP. + + + + + List of manually entered DNS addresses. + + + + + + + + + + + + + + + + + + Indicates if NTP information is to be retrieved by using DHCP. + + + + + List of NTP addresses retrieved by using DHCP. + + + + + List of manually entered NTP addresses. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Dynamic DNS type. + + + + + DNS name. + + + + + Time to live. + + + + + + + + + + + + + + + + + + + + + + + + + + Indicates whether or not an interface is enabled. + + + + + Link configuration. + + + + + Maximum transmission unit. + + + + + IPv4 network interface configuration. + + + + + IPv6 network interface configuration. + + + + + + + + + + + + + + + + + + + + + Indicates whether or not IPv6 is enabled. + + + + + Indicates whether router advertisment is used. + + + + + List of manually added IPv6 addresses. + + + + + DHCP configuration. + + + + + + + + + + Indicates whether or not IPv4 is enabled. + + + + + List of manually added IPv4 addresses. + + + + + Indicates whether or not DHCP is used. + + + + + + + + + + IPv4 address string. + + + + + IPv6 address string. + + + + + + + + + + Unique identifier of network interface. + + + + + Indicates whether the zero-configuration is enabled or not. + + + + + The zero-configuration IPv4 address(es) + + + + + + + + + + + + + Optional array holding the configuration for the second and possibly further interfaces. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + According to IEEE802.11-2007 H.4.1 the RSNA PSK consists of 256 bits, or 64 octets when represented in hex
+ Either Key or Passphrase shall be given, if both are supplied Key shall be used by the device and Passphrase ignored. +
+
+
+ + + + According to IEEE802.11-2007 H.4.1 a pass-phrase is a sequence of between 8 and 63 ASCII-encoded characters and + each character in the pass-phrase must have an encoding in the range of 32 to 126 (decimal),inclusive.
+ If only Passpharse is supplied the Key shall be derived using the algorithm described in IEEE802.11-2007 section H.4 +
+
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + See IEEE802.11 7.3.2.25.2 for details. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Analytics capabilities + + + + + Device capabilities + + + + + Event capabilities + + + + + Imaging capabilities + + + + + Media capabilities + + + + + PTZ capabilities + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Analytics service URI. + + + + + Indicates whether or not rules are supported. + + + + + Indicates whether or not modules are supported. + + + + + + + + + + + + Device service URI. + + + + + Network capabilities. + + + + + System capabilities. + + + + + I/O capabilities. + + + + + Security capabilities. + + + + + + + + + + + + + + + + + + Event service URI. + + + + + Indicates whether or not WS Subscription policy is supported. + + + + + Indicates whether or not WS Pull Point is supported. + + + + + Indicates whether or not WS Pausable Subscription Manager Interface is supported. + + + + + + + + + + + + Number of input connectors. + + + + + Number of relay outputs. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Media service URI. + + + + + Streaming capabilities. + + + + + + + + + + + + + + + + + + + + + Indicates whether or not RTP multicast is supported. + + + + + Indicates whether or not RTP over TCP is supported. + + + + + Indicates whether or not RTP/RTSP/TCP is supported. + + + + + + + + + + + + + + + + + + Maximum number of profiles. + + + + + + + + + + + + Indicates whether or not IP filtering is supported. + + + + + Indicates whether or not zeroconf is supported. + + + + + Indicates whether or not IPv6 is supported. + + + + + Indicates whether or not is supported. + + + + + + + + + + + + + + + + + + + + + + + + + + Indicates whether or not TLS 1.1 is supported. + + + + + Indicates whether or not TLS 1.2 is supported. + + + + + Indicates whether or not onboard key generation is supported. + + + + + Indicates whether or not access policy configuration is supported. + + + + + Indicates whether or not WS-Security X.509 token is supported. + + + + + Indicates whether or not WS-Security SAML token is supported. + + + + + Indicates whether or not WS-Security Kerberos token is supported. + + + + + Indicates whether or not WS-Security REL token is supported. + + + + + + + + + + + + + + + + + + + + + EAP Methods supported by the device. The int values refer to the IANA EAP Registry. + + + + + + + + + + + + Indicates whether or not WS Discovery resolve requests are supported. + + + + + Indicates whether or not WS-Discovery Bye is supported. + + + + + Indicates whether or not remote discovery is supported. + + + + + Indicates whether or not system backup is supported. + + + + + Indicates whether or not system logging is supported. + + + + + Indicates whether or not firmware upgrade is supported. + + + + + Indicates supported ONVIF version(s). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Major version number. + + + + + Two digit minor version number (e.g. X.0.1 maps to "01" and X.2.1 maps to "21" where X stands for Major version number). + + + + + + + + + + Imaging service URI. + + + + + + + + + + + PTZ service URI. + + + + + + + + + + + + + + + + + + + + + + + + + + Indication that the SetLayout command supports only predefined layouts. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The address of the replay service. + + + + + + + + + + + + The address of the receiver service. + + + + + Indicates whether the device can receive RTP multicast streams. + + + + + Indicates whether the device can receive RTP/TCP streams + + + + + Indicates whether the device can receive RTP/RTSP/TCP streams. + + + + + The maximum number of receivers supported by the device. + + + + + The maximum allowed length for RTSP URIs. + + + + + + + + + + + + + Obsolete property. + + + + + + + + + + + + + + + + + + + + + + + Enumeration describing the available system log modes. + + + + + Indicates that a system log is requested. + + + + + Indicates that a access log is requested. + + + + + + + + + + The log information as attachment data. + + + + + The log information as character data. + + + + + + + + + + The support information as attachment data. + + + + + The support information as character data. + + + + + + + + + + base64 encoded binary data. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Enumeration describing the available factory default modes. + + + + + Indicates that a hard factory default is requested. + + + + + Indicates that a soft factory default is requested. + + + + + + + + + + Indicates that the date and time are set manually. + + + + + Indicates that the date and time are set through NTP + + + + + + + + General date time inforamtion returned by the GetSystemDateTime method. + + + + + Indicates if the time is set manully or through NTP. + + + + + Informative indicator whether daylight savings is currently on/off. + + + + + Timezone information in Posix format. + + + + + Current system date and time in UTC format. This field is mandatory since version 2.0. + + + + + Date and time in local format. + + + + + + + + + + + + + + + + + + + + + + + + + + Range is 1 to 12. + + + + + Range is 1 to 31. + + + + + + + + + + Range is 0 to 23. + + + + + Range is 0 to 59. + + + + + Range is 0 to 61 (typically 59). + + + + + + + + + The TZ format is specified by POSIX, please refer to POSIX 1003.1 section 8.3
+ Example: Europe, Paris TZ=CET-1CEST,M3.5.0/2,M10.5.0/3
+ CET = designation for standard time when daylight saving is not in force
+ -1 = offset in hours = negative so 1 hour east of Greenwich meridian
+ CEST = designation when daylight saving is in force ("Central European Summer Time")
+ , = no offset number between code and comma, so default to one hour ahead for daylight saving
+ M3.5.0 = when daylight saving starts = the last Sunday in March (the "5th" week means the last in the month)
+ /2, = the local time when the switch occurs = 2 a.m. in this case
+ M10.5.0 = when daylight saving ends = the last Sunday in October.
+ /3, = the local time when the switch occurs = 3 a.m. in this case
+
+
+ + + + Posix timezone string. + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Username string. + + + + + Password string. + + + + + User level string. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Certificate id. + + + + + base64 encoded DER representation of certificate. + + + + + + + + + + Certificate id. + + + + + Indicates whether or not a certificate is used in a HTTPS configuration. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Validity Range is from "NotBefore" to "NotAfter"; the corresponding DateTimeRange is from "From" to "Until" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + EAP Method type as defined in IANA EAP Registry. + + + + + + + + + + + + + + + + + + + + + Confgiuration information for TLS Method. + + + + + Password for those EAP Methods that require a password. The password shall never be returned on a get method. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 'Bistable' or 'Monostable' +
    +
  • Bistable – After setting the state, the relay remains in this state.
  • +
  • Monostable – After setting the state, the relay returns to its idle state after the specified time.
  • +
+
+
+
+ + + Time after which the relay returns to its idle state if it is in monostable mode. If the Mode field is set to bistable mode the value of the parameter can be ignored. + + + + + + 'open' or 'closed' +
    +
  • 'open' means that the relay is open when the relay state is set to 'inactive' through the trigger command and closed when the state is set to 'active' through the same command.
  • +
  • 'closed' means that the relay is closed when the relay state is set to 'inactive' through the trigger command and open when the state is set to 'active' through the same command.
  • +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A unique identifier that is used to reference PTZ Nodes. + + + + + + + A list of Coordinate Systems available for the PTZ Node. For each Coordinate System, the PTZ Node MUST specify its allowed range. + + + + + + + All preset operations MUST be available for this PTZ Node if one preset is supported. + + + + + + + A boolean operator specifying the availability of a home position. If set to true, the Home Position Operations MUST be available for this PTZ Node. + + + + + + + A list of supported Auxiliary commands. If the list is not empty, the Auxiliary Operations MUST be available for this PTZ Node. + + + + + + + + + Indication whether the HomePosition of a Node is fixed or it can be changed via the SetHomePosition command. + + + + + + + + + + + + + + + Detail of supported Preset Tour feature. + + + + + + + + + + + + + + + + + + Indicates number of preset tours that can be created. Required preset tour operations shall be available for this PTZ Node if one or more preset tour is supported. + + + + + Indicates which preset tour operations are available for this PTZ Node. + + + + + + + + + + + + + + + + + + + + + A mandatory reference to the PTZ Node that the PTZ Configuration belongs to. + + + + + + + If the PTZ Node supports absolute Pan/Tilt movements, it shall specify one Absolute Pan/Tilt Position Space as default. + + + + + + + If the PTZ Node supports absolute zoom movements, it shall specify one Absolute Zoom Position Space as default. + + + + + + + If the PTZ Node supports relative Pan/Tilt movements, it shall specify one RelativePan/Tilt Translation Space as default. + + + + + + + If the PTZ Node supports relative zoom movements, it shall specify one Relative Zoom Translation Space as default. + + + + + + + If the PTZ Node supports continuous Pan/Tilt movements, it shall specify one Continuous Pan/Tilt Velocity Space as default. + + + + + + + If the PTZ Node supports continuous zoom movements, it shall specify one Continuous Zoom Velocity Space as default. + + + + + + + If the PTZ Node supports absolute or relative PTZ movements, it shall specify corresponding default Pan/Tilt and Zoom speeds. + + + + + + + If the PTZ Node supports continuous movements, it shall specify a default timeout, after which the movement stops. + + + + + + + The Pan/Tilt limits element should be present for a PTZ Node that supports an absolute Pan/Tilt. If the element is present it signals the support for configurable Pan/Tilt limits. If limits are enabled, the Pan/Tilt movements shall always stay within the specified range. The Pan/Tilt limits are disabled by setting the limits to –INF or +INF. + + + + + + + The Zoom limits element should be present for a PTZ Node that supports absolute zoom. If the element is present it signals the supports for configurable Zoom limits. If limits are enabled the zoom movements shall always stay within the specified range. The Zoom limits are disabled by settings the limits to -INF and +INF. + + + + + + + + + + + + + + + + + + + + + Optional element to configure PT Control Direction related features. + + + + + + + + + + + + + + + + + Optional element to configure related parameters for E-Flip. + + + + + Optional element to configure related parameters for reversing of PT Control Direction. + + + + + + + + + + + + + + + + + + + Parameter to enable/disable E-Flip feature. + + + + + + + + + + + + Parameter to enable/disable Reverse feature. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A list of supported coordinate systems including their range limitations. + + + + + + + A timeout Range within which Timeouts are accepted by the PTZ Node. + + + + + + + Supported options for PT Direction Control. + + + + + + + + + + + + + + + + + + Supported options for EFlip feature. + + + + + Supported options for Reverse feature. + + + + + + + + + + + + + + + + + + Options of EFlip mode parameter. + + + + + + + + + + + + + + + + + + Options of Reverse mode parameter. + + + + + + + + + + + + + + + + + + + A range of pan tilt limits. + + + + + + + + + + + + A range of zoom limit + + + + + + + + + + + + The Generic Pan/Tilt Position space is provided by every PTZ node that supports absolute Pan/Tilt, since it does not relate to a specific physical range. + Instead, the range should be defined as the full range of the PTZ unit normalized to the range -1 to 1 resulting in the following space description. + + + + + + + The Generic Zoom Position Space is provided by every PTZ node that supports absolute Zoom, since it does not relate to a specific physical range. + Instead, the range should be defined as the full range of the Zoom normalized to the range 0 (wide) to 1 (tele). + There is no assumption about how the generic zoom range is mapped to magnification, FOV or other physical zoom dimension. + + + + + + + The Generic Pan/Tilt translation space is provided by every PTZ node that supports relative Pan/Tilt, since it does not relate to a specific physical range. + Instead, the range should be defined as the full positive and negative translation range of the PTZ unit normalized to the range -1 to 1, + where positive translation would mean clockwise rotation or movement in right/up direction resulting in the following space description. + + + + + + + The Generic Zoom Translation Space is provided by every PTZ node that supports relative Zoom, since it does not relate to a specific physical range. + Instead, the corresponding absolute range should be defined as the full positive and negative translation range of the Zoom normalized to the range -1 to1, + where a positive translation maps to a movement in TELE direction. The translation is signed to indicate direction (negative is to wide, positive is to tele). + There is no assumption about how the generic zoom range is mapped to magnification, FOV or other physical zoom dimension. This results in the following space description. + + + + + + + The generic Pan/Tilt velocity space shall be provided by every PTZ node, since it does not relate to a specific physical range. + Instead, the range should be defined as a range of the PTZ unit’s speed normalized to the range -1 to 1, where a positive velocity would map to clockwise + rotation or movement in the right/up direction. A signed speed can be independently specified for the pan and tilt component resulting in the following space description. + + + + + + + The generic zoom velocity space specifies a zoom factor velocity without knowing the underlying physical model. The range should be normalized from -1 to 1, + where a positive velocity would map to TELE direction. A generic zoom velocity space description resembles the following. + + + + + + + The speed space specifies the speed for a Pan/Tilt movement when moving to an absolute position or to a relative translation. + In contrast to the velocity spaces, speed spaces do not contain any directional information. The speed of a combined Pan/Tilt + movement is represented by a single non-negative scalar value. + + + + + + + The speed space specifies the speed for a Zoom movement when moving to an absolute position or to a relative translation. + In contrast to the velocity spaces, speed spaces do not contain any directional information. + + + + + + + + + + + + + + + + + + + + A URI of coordinate systems. + + + + + + + A range of x-axis. + + + + + + + A range of y-axis. + + + + + + + + + + + + A URI of coordinate systems. + + + + + + + A range of x-axis. + + + + + + + + + + + + + Pan/tilt coordinate space selector. The following options are defined:
    +
  • http://www.onvif.org/ver10/tptz/PanTiltSpaces/PositionGenericSpace
  • +
  • http://www.onvif.org/ver10/tptz/PanTiltSpaces/TranslationGenericSpace
  • +
  • http://www.onvif.org/ver10/tptz/PanTiltSpaces/VelocityGenericSpace
  • +
  • http://www.onvif.org/ver10/tptz/PanTiltSpaces/GenericSpeedSpace
  • +
+
+
+
+
+ + + + + + + Pan/tilt coordinate space selector. The following options are defined:
    +
  • http://www.onvif.org/ver10/tptz/PanTiltSpaces/PositionGenericSpace
  • +
  • http://www.onvif.org/ver10/tptz/PanTiltSpaces/TranslationGenericSpace
  • +
  • http://www.onvif.org/ver10/tptz/PanTiltSpaces/VelocityGenericSpace
  • +
  • http://www.onvif.org/ver10/tptz/PanTiltSpaces/GenericSpeedSpace
  • +
+
+
+
+
+ + + + + + Pan and tilt position. The x component corresponds to pan and the y component to tilt. + + + + + + A zoom position. + + + + + + + + + + + Pan and tilt speed. The x component corresponds to pan and the y component to tilt. If omitted in a request, the current (if any) PanTilt movement should not be affected. + + + + + + A zoom speed. If omitted in a request, the current (if any) Zoom movement should not be affected. + + + + + + + + + + + + Specifies the absolute position of the PTZ unit together with the Space references. The default absolute spaces of the corresponding PTZ configuration MUST be referenced within the Position element. + + + + + + + Indicates if the Pan/Tilt/Zoom device unit is currently moving, idle or in an unknown state. + + + + + + + States a current PTZ error. + + + + + + + Specifies the UTC time when this status was generated. + + + + + + + + + + + + + + A list of preset position name. + + + + + + + A list of preset position. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Readable name of the preset tour. + + + + + Read only parameters to indicate the status of the preset tour. + + + + + Auto Start flag of the preset tour. True allows the preset tour to be activated always. + + + + + Parameters to specify the detail behavior of the preset tour. + + + + + A list of detail of touring spots including preset positions. + + + + + + + Unique identifier of this preset tour. + + + + + + + + + + + + + + + + Detail definition of preset position of the tour spot. + + + + + Optional parameter to specify Pan/Tilt and Zoom speed on moving toward this tour spot. + + + + + Optional parameter to specify time duration of staying on this tour sport. + + + + + + + + + + + + + + + + + + + Option to specify the preset position with Preset Token defined in advance. + + + + + Option to specify the preset position with the home position of this PTZ Node. "False" to this parameter shall be treated as an invalid argument. + + + + + Option to specify the preset position with vector of PTZ node directly. + + + + + + + + + + + + + + + + + + + + Indicates state of this preset tour by Idle/Touring/Paused. + + + + + Indicates a tour spot currently staying. + + + + + + + + + + + + + + + + + + Optional parameter to specify how many times the preset tour is recurred. + + + + + Optional parameter to specify how long time duration the preset tour is recurred. + + + + + Optional parameter to choose which direction the preset tour goes. Forward shall be chosen in case it is omitted. + + + + + + + + + + + + + + + + + + Indicates whether or not the AutoStart is supported. + + + + + Supported options for Preset Tour Starting Condition. + + + + + Supported options for Preset Tour Spot. + + + + + + + + + + + + Supported options for detail definition of preset position of the tour spot. + + + + + Supported range of stay time for a tour spot. + + + + + + + + + + + + A list of available Preset Tokens for tour spots. + + + + + An option to indicate Home postion for tour spots. + + + + + Supported range of Pan and Tilt for tour spots. + + + + + Supported range of Zoom for a tour spot. + + + + + + + + + + + + + + + + + + Supported range of Recurring Time. + + + + + Supported range of Recurring Duration. + + + + + Supported options for Direction of Preset Tour. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Status of focus position. + + + + + + + Status of focus MoveStatus. + + + + + + + Error status of focus. + + + + + + + + + + + + + + + Parameter to set autofocus near limit (unit: meter). + + + + + Parameter to set autofocus far limit (unit: meter). +If set to 0.0, infinity will be used. + + + + + + + + + + + + + + + + + + + Enabled/disabled BLC mode (on/off). + + + + + Image brightness (unit unspecified). + + + + + Color saturation of the image (unit unspecified). + + + + + Contrast of the image (unit unspecified). + + + + + Exposure mode of the device. + + + + + Focus configuration. + + + + + Infrared Cutoff Filter settings. + + + + + Sharpness of the Video image. + + + + + WDR settings. + + + + + White balance settings. + + + + + + + + + + + + + + + + + + + Exposure Mode +
    +
  • Auto – Enabled the exposure algorithm on the NVT.
  • +
  • Manual – Disabled exposure algorithm on the NVT.
  • +
+
+
+
+ + + + The exposure priority mode (low noise/framerate). + + + + + + + Rectangular exposure mask. + + + + + + + Minimum value of exposure time range allowed to be used by the algorithm. + + + + + + + Maximum value of exposure time range allowed to be used by the algorithm. + + + + + + + Minimum value of the sensor gain range that is allowed to be used by the algorithm. + + + + + + + Maximum value of the sensor gain range that is allowed to be used by the algorithm. + + + + + + + Minimum value of the iris range allowed to be used by the algorithm. + + + + + + + Maximum value of the iris range allowed to be used by the algorithm. + + + + + + + The fixed exposure time used by the image sensor (μs). + + + + + + + The fixed gain used by the image sensor (dB). + + + + + + + The fixed attenuation of input light affected by the iris (dB). 0dB maps to a fully opened iris. + + + +
+
+ + + + + + + + + + + + + + White dynamic range (on/off) + + + + + + + Optional level parameter (unitless) + + + + + + + + + Enumeration describing the available backlight compenstation modes. + + + + + Backlight compensation is disabled. + + + + + Backlight compensation is enabled. + + + + + + + + + + Backlight compensation mode (on/off). + + + + + Optional level parameter (unit unspecified). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Parameters for the absolute focus control. + + + + + + + Parameters for the relative focus control. + + + + + + + Parameter for the continuous focus control. + + + + + + + + + + + + Position parameter for the absolute focus control. + + + + + + + Speed parameter for the absolute focus control. + + + + + + + + + + + + Distance parameter for the relative focus control. + + + + + + + Speed parameter for the relative focus control. + + + + + + + + + + + + Speed parameter for the Continuous focus control. + + + + + + + + + + + + + + + + + + + + Valid ranges of the position. + + + + + + + Valid ranges of the speed. + + + + + + + + + + + + Valid ranges of the distance. + + + + + + + Valid ranges of the speed. + + + + + + + + + + + + Valid ranges of the speed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Auto whitebalancing mode (auto/manual). + + + + + Rgain (unitless). + + + + + Bgain (unitless). + + + + + + + + + + + + + + + + + + Status of focus. + + + + + + + + + + + + + + + + + + + + Status of focus position. + + + + + + + Status of focus MoveStatus. + + + + + + + Error status of focus. + + + + + + + + + + + + + + + + + Type describing the ImagingSettings of a VideoSource. The supported options and ranges can be obtained via the GetOptions command. + + + + + Enabled/disabled BLC mode (on/off). + + + + + Image brightness (unit unspecified). + + + + + Color saturation of the image (unit unspecified). + + + + + Contrast of the image (unit unspecified). + + + + + Exposure mode of the device. + + + + + Focus configuration. + + + + + Infrared Cutoff Filter settings. + + + + + Sharpness of the Video image. + + + + + WDR settings. + + + + + White balance settings. + + + + + + + + + + + + + Optional element to configure Image Stabilization feature. + + + + + + + + + + + An optional parameter applied to only auto mode to adjust timing of toggling Ir cut filter. + + + + + + + + + + + + + + + + + Parameter to enable/disable Image Stabilization feature. + + + + + Optional level parameter (unit unspecified) + + + + + + + + + + + + + + + + + + + + + + + + + + + Specifies which boundaries to automatically toggle Ir cut filter following parameters are applied to. Its options shall be chosen from tt:IrCutFilterAutoBoundaryType. + + + + + Adjusts boundary exposure level for toggling Ir cut filter to on/off specified with unitless normalized value from +1.0 to -1.0. Zero is default and -1.0 is the darkest adjustment (Unitless). + + + + + Delay time of toggling Ir cut filter to on/off after crossing of the boundary exposure levels. + + + + + + + + + + + + + + + + + + + + + + + + + Type describing whether WDR mode is enabled or disabled (on/off). + + + + + Wide dynamic range mode (on/off). + + + + + Optional level parameter (unit unspecified). + + + + + + + + Type describing whether BLC mode is enabled or disabled (on/off). + + + + + Backlight compensation mode (on/off). + + + + + Optional level parameter (unit unspecified). + + + + + + + + Type describing the exposure settings. + + + + + + Exposure Mode +
    +
  • Auto – Enabled the exposure algorithm on the device.
  • +
  • Manual – Disabled exposure algorithm on the device.
  • +
+
+
+
+ + + + The exposure priority mode (low noise/framerate). + + + + + + + Rectangular exposure mask. + + + + + + + Minimum value of exposure time range allowed to be used by the algorithm. + + + + + + + Maximum value of exposure time range allowed to be used by the algorithm. + + + + + + + Minimum value of the sensor gain range that is allowed to be used by the algorithm. + + + + + + + Maximum value of the sensor gain range that is allowed to be used by the algorithm. + + + + + + + Minimum value of the iris range allowed to be used by the algorithm. + + + + + + + Maximum value of the iris range allowed to be used by the algorithm. + + + + + + + The fixed exposure time used by the image sensor (μs). + + + + + + + The fixed gain used by the image sensor (dB). + + + + + + + The fixed attenuation of input light affected by the iris (dB). 0dB maps to a fully opened iris. + + + +
+
+ + + + + + + Valid range of Backlight Compensation. + + + + + + + Valid range of Brightness. + + + + + + + Valid range of Color Saturation. + + + + + + + Valid range of Contrast. + + + + + + + Valid range of Exposure. + + + + + + + Valid range of Focus. + + + + + + + Valid range of IrCutFilterModes. + + + + + + + Valid range of Sharpness. + + + + + + + Valid range of WideDynamicRange. + + + + + + + Valid range of WhiteBalance. + + + + + + + + + + + + + + Options of parameters for Image Stabilization feature. + + + + + + + + + + + Options of parameters for adjustment of Ir cut filter auto mode. + + + + + + + + + + + + + + + + + Supported options of Image Stabilization mode parameter. + + + + + Valid range of the Image Stabilization. + + + + + + + + + + + + + + + + + + Supported options of boundary types for adjustment of Ir cut filter auto mode. The opptions shall be chosen from tt:IrCutFilterAutoBoundaryType. + + + + + Indicates whether or not boundary offset for toggling Ir cut filter is supported. + + + + + Supported range of delay time for toggling Ir cut filter. + + + + + + + + + + + + + + + + + + + + + + + + + + 'ON' or 'OFF' + + + + + + + Level range of BacklightCompensation. + + + + + + + + + + + + Exposure Mode +
    +
  • Auto – Enabled the exposure algorithm on the device.
  • +
  • Manual – Disabled exposure algorithm on the device.
  • +
+
+
+
+ + + + The exposure priority mode (low noise/framerate). + + + + + + + Valid range of the Minimum ExposureTime. + + + + + + + Valid range of the Maximum ExposureTime. + + + + + + + Valid range of the Minimum Gain. + + + + + + + Valid range of the Maximum Gain. + + + + + + + Valid range of the Minimum Iris. + + + + + + + Valid range of the Maximum Iris. + + + + + + + Valid range of the ExposureTime. + + + + + + + Valid range of the Gain. + + + + + + + Valid range of the Iris. + + + +
+
+ + + + + + + Valid ranges for the absolute control. + + + + + + + Valid ranges for the relative control. + + + + + + + Valid ranges for the continuous control. + + + + + + + + + + + + Valid ranges of the distance. + + + + + + + Valid ranges of the speed. + + + + + + + + + + + + 'AUTO' or 'MANUAL' + + + + + + + Rgain (unitless). + + + + + + + Bgain (unitless). + + + + + + + + + + + + + + + + + + + + Mode of auto fucus. +
    +
  • AUTO
  • +
  • MANUAL
  • +
+
+
+
+ + + + Parameter to set autofocus near limit (unit: meter). + + + + + Parameter to set autofocus far limit (unit: meter). + + + +
+ +
+ + + + + + + + + + + + + Mode of WhiteBalance. +
    +
  • AUTO
  • +
  • MANUAL
  • +
+
+
+
+ + + +
+
+ + + + + + + + + + + + + Mode of Auto Focus. +
    +
  • AUTO
  • +
  • MANUAL
  • +
+
+
+
+ + + + Valid range of DefaultSpeed. + + + + + + + Valid range of NearLimit. + + + + + + + Valid range of FarLimit. + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Token value pairs that triggered this message. Typically only one item is present. + + + + + + + + + + + + + + + + + + + + + + List of parameters according to the corresponding ItemListDescription. + Each item in the list shall have a unique name. + + + + + + Value name pair as defined by the corresponding description. + + + + + Item name. + + + + + Item value. The type is defined in the corresponding description. + + + + + + + Complex value structure. + + + + + + XML tree contiaing the element value as defined in the corresponding description. + + + + + + Item name. + + + + + + + + + + + + + + + + + + + + + + Set of tokens producing this message. The list may only contain SimpleItemDescription items. + The set of tokens identify the component within the WS-Endpoint, which is responsible for the producing the message.
+ For analytics events the token set shall include the VideoSourceConfigurationToken, the VideoAnalyticsConfigurationToken + and the name of the analytics module or rule. +
+
+
+ + + Describes optional message payload parameters that may be used as key. E.g. object IDs of tracked objects are conveyed as key. + + + + + Describes the payload of the message. + + + +
+ + + Must be set to true when the described Message relates to a property. An alternative term of "property" is a "state" in contrast to a pure event, which contains relevant information for only a single point in time.
Default is false.
+
+
+ +
+ + + + + + + + + + + Describes a list of items. Each item in the list shall have a unique name. + The list is designed as linear structure without optional or unbounded elements. + Use ElementItems only when complex structures are inevitable. + + + + + + Description of a simple item. The type must be of cathegory simpleType (xs:string, xs:integer, xs:float, ...). + + + + + Item name. Must be unique within a list. + + + + + + + + + Description of a complex type. The Type must reference a defined type. + + + + + + Item name. Must be unique within a list. + + + + + The type of the item. The Type must reference a defined type. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Object Class Type + + + + + A likelihood/probability that the corresponding object belongs to this class. The sum of the likelihoods shall NOT exceed 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Number of columns of the cell grid (x dimension) + + + + + Number of rows of the cell grid (y dimension) + + + + + A “1” denotes a cell where motion is detected and a “0” an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + List of configuration parameters as defined in the correspding description. + + + + + + Name of the configuration. + + + + + Type of the configuration represented by a unique QName. The Type characterizes a ConfigDescription defining the Parameters. + + + + + + + + + + List describing the configuration parameters. The names of the parameters must be unique. If possible SimpleItems + should be used to transport the information to ease parsing of dynamically defined messages by a client + application. + + + + + + + The analytics modules and rule engine produce Events, which must be listed within the Analytics Module Description. In order to do so + the structure of the Message is defined and consists of three groups: Source, Key, and Data. It is recommended to use SimpleItemDescriptions wherever applicable. + The name of all Items must be unique within all Items contained in any group of this Message. + Depending on the component multiple parameters or none may be needed to identify the component uniquely. + + + + + + + + + + The ParentTopic labels the message (e.g. "nn:RuleEngine/LineCrossing"). The real message can extend the ParentTopic + by for example the name of the instaniated rule (e.g. "nn:RuleEngine/LineCrossing/corssMyFirstLine"). + Even without knowing the complete topic name, the subscriber will be able to distiguish the + messages produced by different rule instances of the same type via the Source fields of the message. + There the name of the rule instance, which produced the message, must be listed. + + + + + + + + + + + + + XML Type of the Configuration (e.g. "tt::LineDetector"). + + + + + + + + + + + + + + + + Lists the location of all schemas that are referenced in the rules. + + + + + List of rules supported by the Video Analytics configuration.. + + + + + + + + + + + + + + + + + + It optionally contains a list of URLs that provide the location of schema files. + These schema files describe the types and elements used in the analytics module descriptions. + If the analytics module descriptions reference types or elements of the ONVIF schema file, + the ONVIF schema file MUST be explicitly listed. + + + + + + + + + + + + + + + + + + + Contains Polygon configuration for rule parameters + + + + + + + + + + + + Contains array of Polyline + + + + + + + + + + + + + + + + + + Contains PolylineArray configuration data + + + + + + + + + + + + Motion Expression data structure contains motion expression which is based on Scene Descriptor schema with XPATH syntax. The Type argument could allow introduction of different dialects + + + + + + + + + + + + + Contains Rule MotionExpression configuration + + + + + + + + + + + + Mapping of the cell grid to the Video frame. The cell grid is starting from the upper left corner and x dimension is going from left to right and the y dimension from up to down. + + + + + + + Number of columns of the cell grid (x dimension) + + + + + Number of rows of the cell grid (y dimension) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configuration of the streaming and coding settings of a Video window. + + + + + Optional name of the pane configuration. + + + + + If the device has audio outputs, this element contains a pointer to the audio output that is associated with the pane. A client +can retrieve the available audio outputs of a device using the GetAudioOutputs command of the DeviceIO service. + + + + + If the device has audio sources, this element contains a pointer to the audio source that is associated with this pane. +The audio connection from a decoder device to the NVT is established using the backchannel mechanism. A client can retrieve the available audio sources of a device using the GetAudioSources command of the +DeviceIO service. + + + + + The configuration of the audio encoder including codec, bitrate +and sample rate. + + + + + A pointer to a Receiver that has the necessary information to receive + data from a Transmitter. This Receiver can be connected and the network video decoder displays the received data on the specified outputs. A client can retrieve the available Receivers using the + GetReceivers command of the Receiver Service. + + + + + A unique identifier in the display device. + + + + + + + + + + A pane layout describes one Video window of a display. It links a pane configuration to a region of the screen. + + + + + Reference to the configuration of the streaming and coding parameters. + + + + + Describes the location and size of the area on the monitor. The area coordinate values are espressed in normalized units [-1.0, 1.0]. + + + + + + + + + + A layout describes a set of Video windows that are displayed simultaniously on a display. + + + + + List of panes assembling the display layout. + + + + + + + + + + + + + + + + This type contains the Audio and Video coding capabilities of a display service. + + + + + If the device supports audio encoding this section describes the supported codecs and their configuration. + + + + + If the device supports audio decoding this section describes the supported codecs and their settings. + + + + + This section describes the supported video codesc and their configuration. + + + + + + + + + + The options supported for a display layout. + + + + + Lists the possible Pane Layouts of the Video Output + + + + + + + + + + + + + + + + Description of a pane layout describing a complete display layout. + + + + + List of areas assembling a layout. Coordinate values are in the range [-1.0, 1.0]. + + + + + + + + + + + + + + + + + + + + + + Description of a receiver, including its token and configuration. + + + + + + Unique identifier of the receiver. + + + + + Describes the configuration of the receiver. + + + + + + + + + + + Describes the configuration of a receiver. + + + + + + The following connection modes are defined: + + + + + Details of the URI to which the receiver should connect. + + + + + Stream connection parameters. + + + + + + + + + + + Specifies a receiver connection mode. + + + + + + The receiver connects on demand, as required by consumers of the media streams. + + + + + The receiver attempts to maintain a persistent connection to the configured endpoint. + + + + + The receiver does not attempt to connect. + + + + + This case should never happen. + + + + + + + + + Specifies the current connection state of the receiver. + + + + + + The receiver is not connected. + + + + + The receiver is attempting to connect. + + + + + The receiver is connected. + + + + + This case should never happen. + + + + + + + + + Contains information about a receiver's current state. + + + + + + The connection state of the receiver may have one of the following states: + + + + + Indicates whether or not the receiver was created automatically. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The earliest point in time where there is recorded data on the device. + + + + + The most recent point in time where there is recorded data on the device. + + + + + The device contains this many recordings. + + + + + + + + + + A structure for defining a limited scope when searching in recorded data. + + + + + A list of sources that are included in the scope. If this list is included, only data from one of these sources shall be searched. + + + + + A list of recordings that are included in the scope. If this list is included, only data from one of these recordings shall be searched. + + + + + An xpath expression used to specify what recordings to search. Only those recordings with an RecordingInformation structure that matches the filter shall be searched. + + + + + Extension point + + + + + + + + + + + + + + + + + + + + + + + + + The lower boundary of the PTZ volume to look for. + + + + + The upper boundary of the PTZ volume to look for. + + + + + If true, search for when entering the specified PTZ volume. + + + + + + + + + + + + + + + + + + + + + + + + The state of the search when the result is returned. Indicates if there can be more results, or if the search is completed. + + + + + A RecordingInformation structure for each found recording matching the search. + + + + + + + + + + The state of the search when the result is returned. Indicates if there can be more results, or if the search is completed. + + + + + A FindEventResult structure for each found event matching the search. + + + + + + + + + + The recording where this event was found. Empty string if no recording is associated with this event. + + + + + A reference to the track where this event was found. Empty string if no track is associated with this event. + + + + + The time when the event occured. + + + + + The description of the event. + + + + + If true, indicates that the event is a virtual event generated for this particular search session to give the state of a property at the start time of the search. + + + + + + + + + + + + The state of the search when the result is returned. Indicates if there can be more results, or if the search is completed. + + + + + A FindPTZPositionResult structure for each found PTZ position matching the search. + + + + + + + + + + A reference to the recording containing the PTZ position. + + + + + A reference to the metadata track containing the PTZ position. + + + + + The time when the PTZ position was valid. + + + + + The PTZ position. + + + + + + + + + + + + The state of the search when the result is returned. Indicates if there can be more results, or if the search is completed. + + + + + A FindMetadataResult structure for each found set of Metadata matching the search. + + + + + + + + + + A reference to the recording containing the metadata. + + + + + A reference to the metadata track containing the matching metadata. + + + + + The point in time when the matching metadata occurs in the metadata track. + + + + + + + + + + + + The search is queued and not yet started. + + + + + The search is underway and not yet completed. + + + + + The search has been completed and no new results will be found. + + + + + The state of the search is unknown. (This is not a valid response from GetSearchState.) + + + + + + + + + + + + + + + + Information about the source of the recording. This gives a description of where the data in the recording comes from. Since a single + recording is intended to record related material, there is just one source. It is indicates the physical location or the + major data source for the recording. Currently the recordingconfiguration cannot describe each individual data source. + + + + + + + + + Basic information about the track. Note that a track may represent a single contiguous time span or consist of multiple slices. + + + + + + + + + + + + A set of informative desciptions of a data source. The Search searvice allows a client to filter on recordings based on information in this structure. + + + + + + + Identifier for the source chosen by the client that creates the structure. + This identifier is opaque to the device. Clients may use any type of URI for this field. A device shall support at least 128 characters. + + + + + Informative user readable name of the source, e.g. "Camera23". A device shall support at least 20 characters. + + + + + Informative description of the physical location of the source, e.g. the coordinates on a map. + + + + + Informative description of the source. + + + + + URI provided by the service supplying data to be recorded. A device shall support at least 128 characters. + + + + + + + + + + + + + + + + + This case should never happen. + + + + + + + + + + + Type of the track: "Video", "Audio" or "Metadata". + The track shall only be able to hold data of that type. + + + + + Informative description of the contents of the track. + + + + + The start date and time of the oldest recorded data in the track. + + + + + The stop date and time of the newest recorded data in the track. + + + + + + + + + + + + + + + Placeholder for future extension. + + + + + + + + A set of media attributes valid for a recording at a point in time or for a time interval. + + + + + A reference to the recording that has these attributes. + + + + + A set of attributes for each track. + + + + + The attributes are valid from this point in time in the recording. + + + + + The attributes are valid until this point in time in the recording. Can be equal to 'From' to indicate that the attributes are only known to be valid for this particular point in time. + + + + + + + + + + + + The basic information about the track. Note that a track may represent a single contiguous time span or consist of multiple slices. + + + + + If the track is a video track, exactly one of this structure shall be present and contain the video attributes. + + + + + If the track is an audio track, exactly one of this structure shall be present and contain the audio attributes. + + + + + If the track is an metadata track, exactly one of this structure shall be present and contain the metadata attributes. + + + + + + + + + + + + + + + + + + + + + + Average bitrate in kbps. + + + + + The width of the video in pixels. + + + + + The height of the video in pixels. + + + + + Used video codec, either Jpeg, H.264 or Mpeg4 + + + + + Average framerate in frames per second. + + + + + + + + + + + + The bitrate in kbps. + + + + + Audio codec used for encoding the audio (either G.711, G.726 or AAC) + + + + + The sample rate in kHz. + + + + + + + + + + + + Indicates that there can be PTZ data in the metadata track in the specified time interval. + + + + + Indicates that there can be analytics data in the metadata track in the specified time interval. + + + + + Indicates that there can be notifications in the metadata track in the specified time interval. + + + + + + + List of all PTZ spaces active for recording. Note that events are only recorded on position changes and the actual point of recording may not necessarily contain an event of the specified type. + + + + + + + + + + + + + + + + + Information about the source of the recording. + + + + + Informative description of the source. + + + + + Sspecifies the maximum time that data in any track within the + recording shall be stored. The device shall delete any data older than the maximum retention + time. Such data shall not be accessible anymore. If the MaximumRetentionPeriod is set to 0, + the device shall not limit the retention time of stored data, except by resource constraints. + Whatever the value of MaximumRetentionTime, the device may automatically delete + recordings to free up storage space for new recordings. + + + + + + + + + + + + Type of the track. It shall be equal to the strings “Video”, + “Audio” or “Metadata”. The track shall only be able to hold data of that type. + + + + + Informative description of the track. + + + + + + + + + + + + Token of the recording. + + + + + Configuration of the recording. + + + + + List of tracks. + + + + + + + + + + + + Configuration of a track. + + + + + + + + + + + Token of the track. + + + + + Configuration of the track. + + + + + + + + + + + + Identifies the recording to which this job shall store the received data. + + + + + The mode of the job. If it is idle, nothing shall happen. If it is active, the device shall try + to obtain data from the receivers. A client shall use GetRecordingJobState to determine if data transfer is really taking place.
+ The only valid values for Mode shall be “Idle” and “Active”.
+
+
+ + + This shall be a non-negative number. If there are multiple recording jobs that store data to + the same track, the device will only store the data for the recording job with the highest + priority. The priority is specified per recording job, but the device shall determine the priority + of each track individually. If there are two recording jobs with the same priority, the device + shall record the data corresponding to the recording job that was activated the latest. + + + + + Source of the recording. + + + +
+ +
+ + + + + + + + + + + + + + + + This field shall be a reference to the source of the data. The type of the source + is determined by the attribute Type in the SourceToken structure. If Type is + http://www.onvif.org/ver10/schema/Receiver, the token is a ReceiverReference. In this case + the device shall receive the data over the network. If Type is + http://www.onvif.org/ver10/schema/Profile, the token identifies a media profile, instructing the + device to obtain data from a profile that exists on the local device. + + + + + If this field is TRUE, and if the SourceToken is omitted, the device + shall create a receiver object (through the receiver service) and assign the + ReceiverReference to the SourceToken field. When retrieving the RecordingJobConfiguration + from the device, the AutoCreateReceiver field shall never be present. + + + + + List of tracks associated with the recording. + + + + + + + + + + + + + + + + + + If the received RTSP stream contains multiple tracks of the same type, the + SourceTag differentiates between those Tracks. This field can be ignored in case of recording a local source. + + + + + The destination is the tracktoken of the track to which the device shall store the + received data. + + + + + + + + + + + + Identification of the recording that the recording job records to. + + + + + Holds the aggregated state over the whole RecordingJobInformation structure. + + + + + Identifies the data source of the recording job. + + + + + + + + + + + + + + + + + + + + + + Identifies the data source of the recording job. + + + + + Holds the aggregated state over all substructures of RecordingJobStateSource. + + + + + List of track items. + + + + + + + + + + + + + + + + + + + Identifies the track of the data source that provides the data. + + + + + Indicates the destination track. + + + + + Optionally holds an implementation defined string value that describes the error. + The string should be in the English language. + + + + + Provides the job state of the track. The valid + values of state shall be “Idle”, “Active” and “Error”. If state equals “Error”, the Error field may be filled in with an implementation defined value. + + + + + + + + + + + + + + + + + + + + + + + + + + Configuration parameters for the replay service. + + + + + + The RTSP session timeout. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Token of the analytics engine (AnalyticsEngine) being controlled. + + + + + Token of the analytics engine configuration (VideoAnalyticsConfiguration) in effect. + + + + + Tokens of the input (AnalyticsEngineInput) configuration applied. + + + + + Tokens of the receiver providing media input data. The order of ReceiverToken shall exactly match the order of InputToken. + + + + + + + + + + + + + + + + + + + This case should never happen. + + + + + + + + + + Token of the control object whose status is requested. + + + + + + + + + + + + + + + + + + + + + + + + + + Action Engine Event Payload data structure contains the information about the ONVIF command invocations. Since this event could be generated by other or proprietary actions, the command invocation specific fields are defined as optional and additional extension mechanism is provided for future or additional action definitions. + + + + + Request Message + + + + + Response Message + + + + + Fault Message + + + + + + + + + + + + + + + + + + + + + + + AudioClassType acceptable values are; + gun_shot, scream, glass_breaking, tire_screech + + + + + + + + + + Indicates audio class label + + + + + A likelihood/probability that the corresponding audio event belongs to this class. The sum of the likelihoods shall NOT exceed 1 + + + + + + + + + + + + Array of audio class label and class probability + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + For OSD position type, following are the pre-defined:
  • UpperLeft
  • +
  • UpperRight
  • +
  • LowerLeft
  • +
  • LowerRight
  • +
  • Custom
+
+
+
+ + +
+ +
+ + + + + + + + + + + The value range of "Transparent" could be defined by vendors only should follow this rule: the minimum value means non-transparent and the maximum value maens fully transparent. + + + + + + + + + + + + + + The following OSD Text Type are defined:
    +
  • Plain - The Plain type means the OSD is shown as a text string which defined in the "PlainText" item.
  • +
  • Date - The Date type means the OSD is shown as a date, format of which should be present in the "DateFormat" item.
  • +
  • Time - The Time type means the OSD is shown as a time, format of which should be present in the "TimeFormat" item.
  • +
  • DateAndTime - The DateAndTime type means the OSD is shown as date and time, format of which should be present in the "DateFormat" and the "TimeFormat" item.
  • +
+
+
+
+ + + + List of supported OSD date formats. This element shall be present when the value of Type field has Date or DateAndTime. The following DateFormat are defined:
    +
  • M/d/yyyy - e.g. 3/6/2013
  • +
  • MM/dd/yyyy - e.g. 03/06/2013
  • +
  • dd/MM/yyyy - e.g. 06/03/2013
  • +
  • yyyy/MM/dd - e.g. 2013/03/06
  • +
  • yyyy-MM-dd - e.g. 2013-06-03
  • +
  • dddd, MMMM dd, yyyy - e.g. Wednesday, March 06, 2013
  • +
  • MMMM dd, yyyy - e.g. March 06, 2013
  • +
  • dd MMMM, yyyy - e.g. 06 March, 2013
  • +
+
+
+
+ + + + List of supported OSD time formats. This element shall be present when the value of Type field has Time or DateAndTime. The following TimeFormat are defined:
    +
  • h:mm:ss tt - e.g. 2:14:21 PM
  • +
  • hh:mm:ss tt - e.g. 02:14:21 PM
  • +
  • H:mm:ss - e.g. 14:14:21
  • +
  • HH:mm:ss - e.g. 14:14:21
  • +
+
+
+
+ + + Font size of the text in pt. + + + + + Font color of the text. + + + + + Background color of the text. + + + + + The content of text to be displayed. + + + +
+ +
+ + + + + + + + + + + + + The URI of the image which to be displayed. + + + + + + + + + + + + + + + + + + + + + + + + + + + Describe the option of the color supported. Either list each color or define the range of color value. The following values are acceptable for Colourspace attribute.
  • http://www.onvif.org/ver10/colorspace/YCbCr - YCbCr colourspace
  • +
  • http://www.onvif.org/ver10/colorspace/CIELUV - CIE LUV
  • +
  • http://www.onvif.org/ver10/colorspace/CIELAB - CIE 1976 (L*a*b*)
  • +
  • http://www.onvif.org/ver10/colorspace/HSV - HSV colourspace
+
+
+ + + + + List the supported color. + + + + + Define the rang of color supported. + + + + + +
+ + + + Describe the option of the color and its transparency. + + + + + Optional list of supported colors. + + + + + Range of the transparent level. Larger means more tranparent. + + + + + + + + + + + + + + + + + + + List of supported OSD text type. When a device indicates the supported number relating to Text type in MaximumNumberOfOSDs, the type shall be presented. + + + + + Range of the font size value. + + + + + List of supported date format. + + + + + List of supported time format. + + + + + List of supported font color. + + + + + List of supported background color. + + + + + + + + + + + + + + + + + + + List of avaiable uris of image. + + + + + + + + + + + + + + + + + + + + + Reference to the video source configuration. + + + + + Type of OSD. + + + + + Position configuration of OSD. + + + + + Text configuration of OSD. It shall be present when the value of Type field is Text. + + + + + Image configuration of OSD. It shall be present when the value of Type field is Image + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The maximum number of OSD configurations supported for the specificate video source configuration. If a device limits the number of instances by OSDType, it should indicate the supported number via the related attribute. + + + + + List supported type of OSD configuration. When a device indicates the supported number for each types in MaximumNumberOfOSDs, related type shall be presented. A device shall return Option element relating to listed type. + + + + + List available OSD position type. Following are the pre-defined:
  • UpperLeft
  • +
  • UpperRight
  • +
  • LowerLeft
  • +
  • LowerRight
  • +
  • Custom
+
+
+
+ + + Option of the OSD text configuration. This element shall be returned if the device is signaling the support for Text. + + + + + Option of the OSD image configuration. This element shall be returned if the device is signaling the support for Image. + + + +
+ +
+ + + + + + + + + + +
diff --git a/onvif/wsdl/ptz.wsdl b/onvif/wsdl/ptz.wsdl new file mode 100644 index 000000000..e977619be --- /dev/null +++ b/onvif/wsdl/ptz.wsdl @@ -0,0 +1,1300 @@ + + + + + + + + + + + + + + + + + + + + The capabilities for the PTZ service is returned in the Capabilities element. + + + + + + + + + + + + + Indicates whether or not EFlip is supported. + + + + + Indicates whether or not reversing of PT control direction is supported. + + + + + Indicates support for the GetCompatibleConfigurations command. + + + + + + + + + + + + + + + A list of the existing PTZ Nodes on the device. + + + + + + + + + + + + + Token of the requested PTZNode. + + + + + + + + + + + + A requested PTZNode. + + + + + + + + + + + + + + + + A list of all existing PTZConfigurations on the device. + + + + + + + + + + + + + Token of the requested PTZConfiguration. + + + + + + + + + + + + A requested PTZConfiguration. + + + + + + + + + + + + + + + + + + + Flag that makes configuration persistent. Example: User wants the configuration to exist after reboot. + + + + + + + + + + + + + + + + + + Token of an existing configuration that the options are intended for. + + + + + + + + + + + + The requested PTZ configuration options. + + + + + + + + + + + + + A reference to the MediaProfile where the operation should take place. + + + + + + The Auxiliary request data. + + + + + + + + + + + + The response contains the auxiliary response. + + + + + + + + + + + + + A reference to the MediaProfile where the operation should take place. + + + + + + + + + + + + A list of presets which are available for the requested MediaProfile. + + + + + + + + + + + + + A reference to the MediaProfile where the operation should take place. + + + + + + A requested preset name. + + + + + + A requested preset token. + + + + + + + + + + + + A token to the Preset which has been set. + + + + + + + + + + + + + A reference to the MediaProfile where the operation should take place. + + + + + + A requested preset token. + + + + + + + + + + + + + + + + A reference to the MediaProfile where the operation should take place. + + + + + + A requested preset token. + + + + + + A requested speed.The speed parameter can only be specified when Speed Spaces are available for the PTZ Node. + + + + + + + + + + + + + + + + A reference to the MediaProfile where the PTZStatus should be requested. + + + + + + + + + + + + The PTZStatus for the requested MediaProfile. + + + + + + + + + + + + + A reference to the MediaProfile where the operation should take place. + + + + + + A requested speed.The speed parameter can only be specified when Speed Spaces are available for the PTZ Node. + + + + + + + + + + + + + + + + + + A reference to the MediaProfile where the home position should be set. + + + + + + + + + + + + + + + + + + A reference to the MediaProfile. + + + + + + A Velocity vector specifying the velocity of pan, tilt and zoom. + + + + + + An optional Timeout parameter. + + + + + + + + + + + + + + + + + + A reference to the MediaProfile. + + + + + + A positional Translation relative to the current position + + + + + + An optional Speed parameter. + + + + + + + + + + + + + + + + + + A reference to the MediaProfile. + + + + + + A Position vector specifying the absolute target position. + + + + + + An optional Speed. + + + + + + + + + + + + + + + + + + A reference to the MediaProfile that indicate what should be stopped. + + + + + + Set true when we want to stop ongoing pan and tilt movements.If PanTilt arguments are not present, this command stops these movements. + + + + + + Set true when we want to stop ongoing zoom movement.If Zoom arguments are not present, this command stops ongoing zoom movement. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Contains the token of an existing media profile the configurations shall be compatible with. + + + + + + + + + + + A list of all existing PTZConfigurations on the NVT that is suitable to be added to the addressed media profile. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Returns the capabilities of the PTZ service. The result is returned in a typed answer. + + + + + + Get the descriptions of the available PTZ Nodes. +
+ A PTZ-capable device may have multiple PTZ Nodes. The PTZ Nodes may represent + mechanical PTZ drivers, uploaded PTZ drivers or digital PTZ drivers. PTZ Nodes are the + lowest level entities in the PTZ control API and reflect the supported PTZ capabilities. The + PTZ Node is referenced either by its name or by its reference token. +
+ + +
+ + Get a specific PTZ Node identified by a reference + token or a name. + + + + + + Get a specific PTZonfiguration from the device, identified by its reference token or name. +
+ The default Position/Translation/Velocity Spaces are introduced to allow NVCs sending move + requests without the need to specify a certain coordinate system. The default Speeds are + introduced to control the speed of move requests (absolute, relative, preset), where no + explicit speed has been set.
+ The allowed pan and tilt range for Pan/Tilt Limits is defined by a two-dimensional space range + that is mapped to a specific Absolute Pan/Tilt Position Space. At least one Pan/Tilt Position + Space is required by the PTZNode to support Pan/Tilt limits. The limits apply to all supported + absolute, relative and continuous Pan/Tilt movements. The limits shall be checked within the + coordinate system for which the limits have been specified. That means that even if + movements are specified in a different coordinate system, the requested movements shall be + transformed to the coordinate system of the limits where the limits can be checked. When a + relative or continuous movements is specified, which would leave the specified limits, the PTZ + unit has to move along the specified limits. The Zoom Limits have to be interpreted + accordingly. +
+ + +
+ + + Get all the existing PTZConfigurations from the device. +
+ The default Position/Translation/Velocity Spaces are introduced to allow NVCs sending move + requests without the need to specify a certain coordinate system. The default Speeds are + introduced to control the speed of move requests (absolute, relative, preset), where no + explicit speed has been set.
+ The allowed pan and tilt range for Pan/Tilt Limits is defined by a two-dimensional space range + that is mapped to a specific Absolute Pan/Tilt Position Space. At least one Pan/Tilt Position + Space is required by the PTZNode to support Pan/Tilt limits. The limits apply to all supported + absolute, relative and continuous Pan/Tilt movements. The limits shall be checked within the + coordinate system for which the limits have been specified. That means that even if + movements are specified in a different coordinate system, the requested movements shall be + transformed to the coordinate system of the limits where the limits can be checked. When a + relative or continuous movements is specified, which would leave the specified limits, the PTZ + unit has to move along the specified limits. The Zoom Limits have to be interpreted + accordingly. +
+ + +
+ + + Set/update a existing PTZConfiguration on the device. + + + + + + + List supported coordinate systems including their range limitations. Therefore, the options + MAY differ depending on whether the PTZ Configuration is assigned to a Profile containing a + Video Source Configuration. In that case, the options may additionally contain coordinate + systems referring to the image coordinate system described by the Video Source + Configuration. If the PTZ Node supports continuous movements, it shall return a Timeout Range within + which Timeouts are accepted by the PTZ Node. + + + + + + + Operation to send auxiliary commands to the PTZ device + mapped by the PTZNode in the selected profile. The + operation is supported + if the AuxiliarySupported element of the PTZNode is true + + + + + + + Operation to request all PTZ presets for the PTZNode + in the selected profile. The operation is supported if there is support + for at least on PTZ preset by the PTZNode. + + + + + + The SetPreset command saves the current device position parameters so that the device can + move to the saved preset position through the GotoPreset operation. + In order to create a new preset, the SetPresetRequest contains no PresetToken. If creation is + successful, the Response contains the PresetToken which uniquely identifies the Preset. An + existing Preset can be overwritten by specifying the PresetToken of the corresponding Preset. + In both cases (overwriting or creation) an optional PresetName can be specified. The + operation fails if the PTZ device is moving during the SetPreset operation. + The device MAY internally save additional states such as imaging properties in the PTZ + Preset which then should be recalled in the GotoPreset operation. + + + + + + Operation to remove a PTZ preset for the Node in + the + selected profile. The operation is supported if the + PresetPosition + capability exists for teh Node in the + selected profile. + + + + + + + Operation to go to a saved preset position for the + PTZNode in the selected profile. The operation is supported if there is + support for at least on PTZ preset by the PTZNode. + + + + + + Operation to move the PTZ device to it's "home" position. The operation is supported if the HomeSupported element in the PTZNode is true. + + + + + Operation to save current position as the home position. + The SetHomePosition command returns with a failure if the “home” position is fixed and + cannot be overwritten. If the SetHomePosition is successful, it is possible to recall the + Home Position with the GotoHomePosition command. + + + + + Operation for continuous Pan/Tilt and Zoom movements. The operation is supported if the PTZNode supports at least one continuous Pan/Tilt or Zoom space. If the space argument is omitted, the default space set by the PTZConfiguration will be used. + + + + + Operation for Relative Pan/Tilt and Zoom Move. The operation is supported if the PTZNode supports at least one relative Pan/Tilt or Zoom space.
+ The speed argument is optional. If an x/y speed value is given it is up to the device to either use + the x value as absolute resoluting speed vector or to map x and y to the component speed. + If the speed argument is omitted, the default speed set by the PTZConfiguration will be used. +
+ + +
+ + + Operation to request PTZ status for the Node in the + selected profile. + + + + + Operation to move pan,tilt or zoom to a absolute destination.
+ The speed argument is optional. If an x/y speed value is given it is up to the device to either use + the x value as absolute resoluting speed vector or to map x and y to the component speed. + If the speed argument is omitted, the default speed set by the PTZConfiguration will be used. +
+ + +
+ + Operation to stop ongoing pan, tilt and zoom movements of absolute relative and continuous type. +If no stop argument for pan, tilt or zoom is set, the device will stop all ongoing pan, tilt and zoom movements. + + + + + Operation to request PTZ preset tours in the selected media profiles. + + + + + Operation to request a specific PTZ preset tour in the selected media profile. + + + + + Operation to request available options to configure PTZ preset tour. + + + + + Operation to create a preset tour for the selected media profile. + + + + + Operation to modify a preset tour for the selected media profile. + + + + + Operation to perform specific operation on the preset tour in selected media profile. + + + + + Operation to delete a specific preset tour from the media profile. + + + + + Operation to get all available PTZConfigurations that can be added to the referenced media profile.
+ A device providing more than one PTZConfiguration or more than one VideoSourceConfiguration or which has any other resource + interdependency between PTZConfiguration entities and other resources listable in a media profile should implement this operation. + PTZConfiguration entities returned by this operation shall not fail on adding them to the referenced media profile. +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/onvif/wsdl/r-2.xsd b/onvif/wsdl/r-2.xsd new file mode 100644 index 000000000..21cfbd782 --- /dev/null +++ b/onvif/wsdl/r-2.xsd @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onvif/wsdl/rw-2.wsdl b/onvif/wsdl/rw-2.wsdl new file mode 100644 index 000000000..04ad487c2 --- /dev/null +++ b/onvif/wsdl/rw-2.wsdl @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onvif/wsdl/soap-envelop.xsd b/onvif/wsdl/soap-envelop.xsd new file mode 100644 index 000000000..b1a20e08e --- /dev/null +++ b/onvif/wsdl/soap-envelop.xsd @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Prose in the spec does not specify that attributes are allowed on the Body element + + + + + + + + + + + + + + + + + + + + 'encodingStyle' indicates any canonicalization conventions followed in the contents of the containing element. For example, the value 'http://schemas.xmlsoap.org/soap/encoding/' indicates the pattern described in SOAP specification + + + + + + + + + + + + + + + Fault reporting structure + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onvif/wsdl/t-1.xsd b/onvif/wsdl/t-1.xsd new file mode 100644 index 000000000..ae42cfbc7 --- /dev/null +++ b/onvif/wsdl/t-1.xsd @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TopicPathExpression ::= TopicPath ( '|' TopicPath )* + TopicPath ::= RootTopic ChildTopicExpression* + RootTopic ::= NamespacePrefix? ('//')? (NCName | '*') + NamespacePrefix ::= NCName ':' + ChildTopicExpression ::= '/' '/'? (QName | NCName | '*'| '.') + + + + + + + + + + + + + The pattern allows strings matching the following EBNF: + ConcreteTopicPath ::= RootTopic ChildTopic* + RootTopic ::= QName + ChildTopic ::= '/' (QName | NCName) + + + + + + + + + + + + + The pattern allows strings matching the following EBNF: + RootTopic ::= QName + + + + + + + diff --git a/onvif/wsdl/ws-addressing-2004-08.xsd b/onvif/wsdl/ws-addressing-2004-08.xsd new file mode 100644 index 000000000..b7bfaff6a --- /dev/null +++ b/onvif/wsdl/ws-addressing-2004-08.xsd @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + If "Policy" elements from namespace "http://schemas.xmlsoap.org/ws/2002/12/policy#policy" are used, they must appear first (before any extensibility elements). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onvif/wsdl/ws-discovery-1.0.wsdl b/onvif/wsdl/ws-discovery-1.0.wsdl new file mode 100644 index 000000000..3f198c634 --- /dev/null +++ b/onvif/wsdl/ws-discovery-1.0.wsdl @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onvif/wsdl/ws-discovery-1.0.xsd b/onvif/wsdl/ws-discovery-1.0.xsd new file mode 100644 index 000000000..11ac790bb --- /dev/null +++ b/onvif/wsdl/ws-discovery-1.0.xsd @@ -0,0 +1,272 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onvif/wsdl/wsdd-discovery-1.1-schema-os.xsd b/onvif/wsdl/wsdd-discovery-1.1-schema-os.xsd new file mode 100644 index 000000000..3307efc1d --- /dev/null +++ b/onvif/wsdl/wsdd-discovery-1.1-schema-os.xsd @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onvif/wsdl/wsdd-discovery-1.1-wsdl-os.wsdl b/onvif/wsdl/wsdd-discovery-1.1-wsdl-os.wsdl new file mode 100644 index 000000000..36936b2a1 --- /dev/null +++ b/onvif/wsdl/wsdd-discovery-1.1-wsdl-os.wsdl @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onvif/wsdl/wsdiscovery-udp.wsdl b/onvif/wsdl/wsdiscovery-udp.wsdl new file mode 100644 index 000000000..54b9fc92d --- /dev/null +++ b/onvif/wsdl/wsdiscovery-udp.wsdl @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onvif/wsdl/wsdiscovery10-udp.wsdl b/onvif/wsdl/wsdiscovery10-udp.wsdl new file mode 100644 index 000000000..6fddfb822 --- /dev/null +++ b/onvif/wsdl/wsdiscovery10-udp.wsdl @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onvif/wsdl/wsdl.xsd b/onvif/wsdl/wsdl.xsd new file mode 100644 index 000000000..a81ab5691 --- /dev/null +++ b/onvif/wsdl/wsdl.xsd @@ -0,0 +1,314 @@ + + + + + + + + + + + + + + This type is extended by component types to allow them to be documented + + + + + + + + + + + + + This type is extended by component types to allow attributes from other namespaces to be added. + + + + + + + + + + + + + This type is extended by component types to allow elements from other namespaces to be added. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Any top level optional element allowed to appear more then once - any child of definitions element except wsdl:types. Any extensibility element is allowed in any place. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onvif/wsdl/wsdl11soap12.xsd b/onvif/wsdl/wsdl11soap12.xsd new file mode 100644 index 000000000..a5e7a0b94 --- /dev/null +++ b/onvif/wsdl/wsdl11soap12.xsd @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onvif/wsdl/wsnotification.wsdl b/onvif/wsdl/wsnotification.wsdl new file mode 100644 index 000000000..2ce67b2e4 --- /dev/null +++ b/onvif/wsdl/wsnotification.wsdl @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onvif/wsdl/xmlmime.xsd b/onvif/wsdl/xmlmime.xsd new file mode 100644 index 000000000..766a07bd9 --- /dev/null +++ b/onvif/wsdl/xmlmime.xsd @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/onvif/wsdl/xop-include.xsd b/onvif/wsdl/xop-include.xsd new file mode 100644 index 000000000..edb43a086 --- /dev/null +++ b/onvif/wsdl/xop-include.xsd @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt index bd1885fbe..79df1f03a 100644 --- a/scripts/CMakeLists.txt +++ b/scripts/CMakeLists.txt @@ -9,6 +9,7 @@ configure_file(zmcontrol.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmcontrol.pl" @ONLY) configure_file(zmdc.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" @ONLY) configure_file(zmfilter.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" @ONLY) configure_file(zmonvif-probe.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmonvif-probe.pl" @ONLY) +configure_file(zmonvif-trigger.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmonvif-trigger.pl" @ONLY) configure_file(zmpkg.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" @ONLY) configure_file(zmrecover.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmrecover.pl" @ONLY) configure_file(zmtrack.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" @ONLY) @@ -21,26 +22,45 @@ configure_file(zmcamtool.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmcamtool.pl" @ONLY) configure_file(zmsystemctl.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmsystemctl.pl" @ONLY) configure_file(zmtelemetry.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmtelemetry.pl" @ONLY) if(NOT ZM_NO_X10) - configure_file(zmx10.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmx10.pl" @ONLY) -endif(NOT ZM_NO_X10) + configure_file(zmx10.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmx10.pl" @ONLY) +endif() #configure_file(zmdbbackup.in zmdbbackup @ONLY) #configure_file(zmdbrestore.in zmdbrestore @ONLY) configure_file(zm.in "${CMAKE_CURRENT_BINARY_DIR}/zm" @ONLY) #configure_file(zmeventdump.in zmeventdump @ONLY) # Generate man files for the perl scripts destined for the bin folder -file(GLOB perlscripts "*.pl") -FOREACH(PERLSCRIPT ${perlscripts}) +if(BUILD_MAN) + file(GLOB perlscripts "${CMAKE_CURRENT_BINARY_DIR}/*.pl") + foreach(PERLSCRIPT ${perlscripts}) get_filename_component(PERLSCRIPTNAME ${PERLSCRIPT} NAME) - POD2MAN(${PERLSCRIPT} zoneminder-${PERLSCRIPTNAME} 8 ${ZM_MANPAGE_DEST_PREFIX}) -ENDFOREACH(PERLSCRIPT ${perlscripts}) + POD2MAN(${PERLSCRIPT} ${PERLSCRIPTNAME} 8 ${ZM_MANPAGE_DEST_PREFIX}) + endforeach(PERLSCRIPT ${perlscripts}) +endif() # Install the perl scripts -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmaudit.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcontrol.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmonvif-probe.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmrecover.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrigger.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmupdate.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmvideo.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmwatch.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcamtool.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtelemetry.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmstats.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/zmaudit.pl" + "${CMAKE_CURRENT_BINARY_DIR}/zmcontrol.pl" + "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" + "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" + "${CMAKE_CURRENT_BINARY_DIR}/zmonvif-probe.pl" + "${CMAKE_CURRENT_BINARY_DIR}/zmonvif-trigger.pl" + "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" + "${CMAKE_CURRENT_BINARY_DIR}/zmrecover.pl" + "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" + "${CMAKE_CURRENT_BINARY_DIR}/zmtrigger.pl" + "${CMAKE_CURRENT_BINARY_DIR}/zmupdate.pl" + "${CMAKE_CURRENT_BINARY_DIR}/zmvideo.pl" + "${CMAKE_CURRENT_BINARY_DIR}/zmwatch.pl" + "${CMAKE_CURRENT_BINARY_DIR}/zmcamtool.pl" + "${CMAKE_CURRENT_BINARY_DIR}/zmtelemetry.pl" + "${CMAKE_CURRENT_BINARY_DIR}/zmstats.pl" + DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) if(NOT ZM_NO_X10) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmx10.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -endif(NOT ZM_NO_X10) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmx10.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +endif() if(WITH_SYSTEMD) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmsystemctl.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -endif(WITH_SYSTEMD) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmsystemctl.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +endif() diff --git a/scripts/ZoneMinder/CMakeLists.txt b/scripts/ZoneMinder/CMakeLists.txt index 876a33da3..2a8708d2f 100644 --- a/scripts/ZoneMinder/CMakeLists.txt +++ b/scripts/ZoneMinder/CMakeLists.txt @@ -2,14 +2,14 @@ # If this is an out-of-source build, copy the files we need to the binary directory if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Changes" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Makefile.PL" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/MANIFEST" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/META.yml" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/README" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/t" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/lib" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE) -endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Changes" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Makefile.PL" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/MANIFEST" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/META.yml" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/README" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/t" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/lib" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE) +endif() # Create files from the .in files configure_file(lib/ZoneMinder/Base.pm.in "${CMAKE_CURRENT_BINARY_DIR}/lib/ZoneMinder/Base.pm" @ONLY) @@ -19,13 +19,13 @@ configure_file(lib/ZoneMinder/ConfigData.pm.in "${CMAKE_CURRENT_BINARY_DIR}/lib/ configure_file(lib/ZoneMinder/ONVIF.pm.in "${CMAKE_CURRENT_BINARY_DIR}/lib/ZoneMinder/ONVIF.pm" @ONLY) if(CMAKE_VERBOSE_MAKEFILE) - set(MAKEMAKER_NOECHO_COMMAND "") -else(CMAKE_VERBOSE_MAKEFILE) - set(MAKEMAKER_NOECHO_COMMAND "NOECHO=\"1>/dev/null\"") -endif(CMAKE_VERBOSE_MAKEFILE) + set(MAKEMAKER_NOECHO_COMMAND "") +else() + set(MAKEMAKER_NOECHO_COMMAND "NOECHO=\"1>/dev/null\"") +endif() # Add build target for the perl modules -add_custom_target(zmperlmodules ALL perl Makefile.PL ${ZM_PERL_MM_PARMS} FIRST_MAKEFILE=MakefilePerl DESTDIR="${CMAKE_CURRENT_BINARY_DIR}/output" ${MAKEMAKER_NOECHO_COMMAND} COMMAND make -f MakefilePerl pure_install COMMENT "Building ZoneMinder perl modules") +add_custom_target(zmperlmodules ALL perl Makefile.PL ${ZM_PERL_MM_PARMS_FULL} FIRST_MAKEFILE=MakefilePerl DESTDIR="${CMAKE_CURRENT_BINARY_DIR}/output" ${MAKEMAKER_NOECHO_COMMAND} COMMAND make -f MakefilePerl pure_install COMMENT "Building ZoneMinder perl modules") # Add install target for the perl modules install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/output/" DESTINATION "/") diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in index 10e4b4733..849ec5450 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in @@ -76,39 +76,42 @@ BEGIN { require ZoneMinder::Database; # Process name, value pairs from the main config file first - my $config_file = ZM_CONFIG; - process_configfile($config_file); + process_configfile(ZM_CONFIG); # Search for user created config files. If one or more are found then # update the Config hash with those values if ( ZM_CONFIG_SUBDIR and -d ZM_CONFIG_SUBDIR ) { if ( -R ZM_CONFIG_SUBDIR ) { - foreach my $filename ( glob ZM_CONFIG_SUBDIR.'/*.conf' ) { + foreach my $filename (glob ZM_CONFIG_SUBDIR.'/*.conf' ) { process_configfile($filename); } } else { - print( STDERR 'WARNING: ZoneMinder configuration subfolder found but is not readable. Check folder permissions on '.ZM_CONFIG_SUBDIR.".\n" ); + print(STDERR 'WARNING: ZoneMinder configuration subfolder found but is not readable. Check folder permissions on '.ZM_CONFIG_SUBDIR.".\n"); } } my $dbh = ZoneMinder::Database::zmDbConnect(); + die "Unable to connect to DB. ZM Cannot continue.\n" if !$dbh; + my $sql = 'SELECT Name,Value FROM Config'; my $sth = $dbh->prepare_cached($sql) or croak("Can't prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute() or croak("Can't execute: ".$sth->errstr()); - while( my $config = $sth->fetchrow_hashref() ) { + while ( my $config = $sth->fetchrow_hashref() ) { + # If already defined skip it because we want the values in /etc/zm.conf to override the values in Config Table + next if exists $Config{$config->{Name}}; $Config{$config->{Name}} = $config->{Value}; } $sth->finish(); - if ( ! $Config{ZM_SERVER_ID} ) { + if ( !$Config{ZM_SERVER_ID} ) { $Config{ZM_SERVER_ID} = undef; - $sth = $dbh->prepare_cached( 'SELECT * FROM Servers WHERE Name=?' ); + $sth = $dbh->prepare_cached('SELECT * FROM Servers WHERE Name=?'); if ( $Config{ZM_SERVER_NAME} ) { - $res = $sth->execute( $Config{ZM_SERVER_NAME} ); + $res = $sth->execute($Config{ZM_SERVER_NAME}); my $result = $sth->fetchrow_hashref(); $Config{ZM_SERVER_ID} = $$result{Id}; } elsif ( $Config{ZM_SERVER_HOST} ) { - $res = $sth->execute( $Config{ZM_SERVER_HOST} ); + $res = $sth->execute($Config{ZM_SERVER_HOST}); my $result = $sth->fetchrow_hashref(); $Config{ZM_SERVER_ID} = $$result{Id}; } @@ -126,20 +129,20 @@ require ZoneMinder::Database; open( my $CONFIG, '<', $config_file ) or croak("Can't open config file '$config_file': $!"); foreach my $str ( <$CONFIG> ) { - next if ( $str =~ /^\s*$/ ); + next if ( $str =~ /^\s*$/ ); next if ( $str =~ /^\s*#/ ); my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*[\'"]*(.*?)[\'"]*\s*$/; if ( !$name ) { print(STDERR "Warning, bad line in $config_file: $str\n"); next; } # end if - $name =~ tr/a-z/A-Z/; + $name = uc $name; #if ( !$ZoneMinder::ConfigData::options_hash{$name} ) { - #print(STDERR "Warning, unknown config option name $name in $config_file\n"); -#} else { - #print(STDERR "Warning, known config option name $name in $config_file\n"); - #} - $Config{$name} = $value; + #print(STDERR "Warning, unknown config option name $name in $config_file\n"); + #} else { + #print(STDERR "Warning, known config option name $name in $config_file\n"); + #} + $Config{$name} = $value; } # end foreach config line close($CONFIG); } # end sub process_configfile @@ -147,13 +150,13 @@ require ZoneMinder::Database; } # end BEGIN sub loadConfigFromDB { - print( 'Loading config from DB' ); + print('Loading config from DB'); my $dbh = ZoneMinder::Database::zmDbConnect(); if ( !$dbh ) { - print( "Error: unable to load options from database: $DBI::errstr\n" ); - return( 0 ); + print("Error: unable to load options from database: $DBI::errstr\n"); + return(0); } - my $sql = "select * from Config"; + my $sql = 'SELECT * FROM Config'; my $sth = $dbh->prepare_cached( $sql ) or croak( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() @@ -161,13 +164,13 @@ sub loadConfigFromDB { my $option_count = 0; while( my $config = $sth->fetchrow_hashref() ) { my ( $name, $value ) = ( $config->{Name}, $config->{Value} ); -#print( "Name = '$name'\n" ); + #print( "Name = '$name'\n" ); my $option = $options_hash{$name}; if ( !$option ) { warn( "No option '$name' found, removing.\n" ); next; } -#next if ( $option->{category} eq 'hidden' ); + #next if ( $option->{category} eq 'hidden' ); if ( defined($value) ) { if ( $option->{type} == $types{boolean} ) { $option->{value} = $value?'yes':'no'; @@ -200,12 +203,12 @@ sub saveConfigToDB { my $res = $dbh->do( $sql ) or croak( "Can't do '$sql': ".$dbh->errstr() ); - $sql = "replace into Config set Id = ?, Name = ?, Value = ?, Type = ?, DefaultValue = ?, Hint = ?, Pattern = ?, Format = ?, Prompt = ?, Help = ?, Category = ?, Readonly = ?, Requires = ?"; + $sql = "replace into Config set Id = ?, Name = ?, Value = ?, Type = ?, DefaultValue = ?, Hint = ?, Pattern = ?, Format = ?, Prompt = ?, Help = ?, Category = ?, Readonly = ?, Private = ?, Requires = ?"; my $sth = $dbh->prepare_cached( $sql ) or croak( "Can't prepare '$sql': ".$dbh->errstr() ); foreach my $option ( @options ) { -#next if ( $option->{category} eq 'hidden' ); -#print( $option->{name}."\n" ) if ( !$option->{category} ); + #next if ( $option->{category} eq 'hidden' ); + #print( $option->{name}."\n" ) if ( !$option->{category} ); $option->{db_type} = $option->{type}->{db_type}; $option->{db_hint} = $option->{type}->{hint}; $option->{db_pattern} = $option->{type}->{pattern}; @@ -237,6 +240,7 @@ sub saveConfigToDB { $option->{help}, $option->{category}, $option->{readonly} ? 1 : 0, + $option->{private} ? 1 : 0, $option->{db_requires} ) or croak("Can't execute when updating config entry $$option{name}: ".$sth->errstr() ); } # end foreach option diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index cd93dfe15..dc50e8d64 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 @@ -252,7 +252,7 @@ our @options = ( authentication which passes an independently authentication 'remote' user via http. In this case ZoneMinder would use the supplied user without additional authentication provided such a - user is configured ion ZoneMinder. + user is configured in ZoneMinder. `, requires => [ { name=>'ZM_OPT_USE_AUTH', value=>'yes' } ], type => { @@ -304,11 +304,12 @@ our @options = ( { name=>'ZM_AUTH_RELAY', value=>'hashed' } ], type => $types{string}, + private => 1, category => 'system', }, { 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 +347,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 @@ -370,6 +371,28 @@ our @options = ( type => $types{boolean}, category => 'system', }, + { + name => 'ZM_JANUS_SECRET', + default => '', + description => 'Password for Janus streaming administration.', + help => q`This value should be set to a secure password, + and match the admin_key value in janus.plugin.streaming.config. + `, + type => $types{string}, + category => 'system', + }, + { + name => 'ZM_JANUS_PATH', + default => '', + description => 'URL for Janus HTTP/S port', + help => q`Janus requires HTTP/S communication to administer + and initiate h.264 streams. If left blank, this will default to + the ZM hostname, port 8088/janus. This setting is particularly + useful for putting janus behind a reverse proxy. + `, + type => $types{string}, + category => 'system', + }, { name => 'ZM_ENABLE_CSRF_MAGIC', default => 'yes', @@ -462,6 +485,7 @@ our @options = ( {name=>'ZM_OPT_USE_GOOG_RECAPTCHA', value=>'yes'} ], type => $types{string}, + private => 1, category => 'system', }, { @@ -477,11 +501,39 @@ our @options = ( {name=>'ZM_OPT_USE_GOOG_RECAPTCHA', value=>'yes'} ], type => $types{string}, + private => 1, + category => 'system', + }, + { + name => 'ZM_OPT_USE_GEOLOCATION', + description => 'Add geolocation features to ZoneMinder.', + help => 'Whether or not to enable Latitude/Longitude settings on Monitors and enable mapping options.', + type => $types{boolean}, + category => 'system', + }, + { + name => 'ZM_OPT_GEOLOCATION_TILE_PROVIDER', + description => 'Tile provider to use for maps.', + help => 'OpenStreetMaps does not itself provide the images to use in the map. There are many to choose from. Mapbox.com is one example that offers free tiles and has been tested during development of this feature.', + requires => [ + {name=>'ZM_OPT_USE_GEOLOCATION', value=>'yes'} + ], + type => $types{string}, + category => 'system', + }, + { + name => 'ZM_OPT_GEOLOCATION_ACCESS_TOKEN', + description => 'Access Token for the tile provider used for maps.', + help => 'OpenStreetMaps does not itself provide the images to use in the map. There are many to choose from. Mapbox.com is one example that offers free tiles and has been tested during development of this feature. You must go to mapbox.com and sign up and get an access token and cutnpaste it here.', + requires => [ + {name=>'ZM_OPT_USE_GEOLOCATION', value=>'yes'} + ], + type => $types{string}, category => 'system', }, { name => 'ZM_SYSTEM_SHUTDOWN', - default => 'true', + default => '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~~ ~~ @@ -491,6 +543,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', @@ -582,26 +642,6 @@ our @options = ( type => $types{integer}, category => 'images', }, -# Deprecated, now stream quality - { - name => 'ZM_JPEG_IMAGE_QUALITY', - default => '70', - description => q`Set the JPEG quality setting for the streamed 'live' images (1-100)`, - help => q` - When viewing a 'live' stream for a monitor ZoneMinder will grab - an image from the buffer and encode it into JPEG format before - sending it. This option specifies what image quality should be - used to encode these images. A higher number means better - quality but less compression so will take longer to view over a - slow connection. By contrast a low number means quicker to view - images but at the price of lower quality images. This option - does not apply when viewing events or still images as these are - usually just read from disk and so will be encoded at the - quality specified by the previous options. - `, - type => $types{integer}, - category => 'hidden', - }, { name => 'ZM_JPEG_STREAM_QUALITY', default => '70', @@ -785,7 +825,7 @@ our @options = ( }, { name => 'ZM_TIMEZONE', - default => 'UTC', + default => '', description => 'The timezone that php should use.', help => q` This should be set equal to the system timezone of the mysql server`, @@ -877,38 +917,6 @@ our @options = ( type => $types{integer}, category => 'config', }, -# Deprecated, really no longer necessary - { - name => 'ZM_OPT_REMOTE_CAMERAS', - default => 'no', - description => 'Are you going to use remote/networked cameras', - help => q` - ZoneMinder can work with both local cameras, ie. those attached - physically to your computer and remote or network cameras. If - you will be using networked cameras select this option. - `, - type => $types{boolean}, - category => 'hidden', - }, -# Deprecated, now set on a per monitor basis using the Method field - { - name => 'ZM_NETCAM_REGEXPS', - default => 'yes', - description => 'Use regular expression matching with network cameras', - help => q` - Traditionally ZoneMinder has used complex regular regular - expressions to handle the multitude of formats that network - cameras produce. In versions from 1.21.1 the default is to use - a simpler and faster built in pattern matching methodology. - This works well with most networks cameras but if you have - problems you can try the older, but more flexible, regular - expression based method by selecting this option. Note, to use - this method you must have libpcre installed on your system. - `, - requires => [ { name => 'ZM_OPT_REMOTE_CAMERAS', value => 'yes' } ], - type => $types{boolean}, - category => 'hidden', - }, { name => 'ZM_HTTP_VERSION', default => '1.0', @@ -972,6 +980,19 @@ our @options = ( type => $types{integer}, category => 'network', }, + { + name => 'ZM_MIN_RTSP_PORT', + default => '', + description => 'Start of port range to contact for RTSP streaming video.', + help => q` + The beginng of a port range that will be used to offer + RTSP streaming of live captured video. + Each monitor will use this value plus the Monitor Id to stream + content. So a value of 2000 here will cause a stream for Monitor 1 to + hit port 2001.`, + type => $types{integer}, + category => 'network', + }, { name => 'ZM_MIN_RTP_PORT', default => '40200', @@ -1068,7 +1089,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 @@ -1100,7 +1121,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 @@ -1513,6 +1534,26 @@ our @options = ( type => $types{boolean}, category => 'logging', }, + { + name => 'ZM_WEB_NAVBAR_TYPE', + default => 'normal', + description => 'Style of the web console navigation bar', + help => q` + Choose between different navigation bar styles for the web + console. The "normal" style has a menu across the top, which + collapses to a pull down menu on small screens. The "collapsed" + style is collapsed all the time. Instead of a menu across the + top, menu items are accessed from the drop down menu on the + right. + `, + type => { + db_type => 'string', + hint => 'normal|collapsed', + pattern => qr|^([nc])|i, + format => q( ($1 =~ /^n/) ? 'normal' : 'collapsed' ) + }, + category => 'web', + }, { name => 'ZM_WEB_TITLE', default => 'ZoneMinder', @@ -1558,6 +1599,20 @@ our @options = ( type => $types{string}, category => 'web', }, + { + name => 'ZM_HOME_ABOUT', + default => 'yes', + description => 'Whether to enable the ZoneMinder About menu.', + help => q` + When enabled, the ZoneMinder logo in the top left corner of the + navigation bar becomes a menu with links to: the ZoneMinder + website, ZoneMinder Documentation, and the ZoneMinder forum. + End users wishing to rebrand their system may want to disable this + as the menu items are currently hard coded. + `, + type => $types{boolean}, + category => 'web', + }, { name => 'ZM_WEB_CONSOLE_BANNER', default => '', @@ -1574,7 +1629,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 @@ -1604,7 +1659,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 @@ -1940,19 +1995,6 @@ our @options = ( }, category => 'upload', }, - { - name => 'ZM_UPLOAD_FTP_HOST', - default => '', - description => 'The remote server to upload to', - help => q` - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the name, or ip - address, of the server to use. - `, - requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], - type => $types{hostname}, - category => 'hidden', - }, { name => 'ZM_UPLOAD_HOST', default => '', @@ -1981,19 +2023,6 @@ our @options = ( type => $types{integer}, category => 'upload', }, - { - name => 'ZM_UPLOAD_FTP_USER', - default => '', - description => 'Your ftp username', - help => q` - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the username that - ZoneMinder should use to log in for ftp transfer. - `, - requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], - type => $types{alphanum}, - category => 'hidden', - }, { name => 'ZM_UPLOAD_USER', default => '', @@ -2007,19 +2036,6 @@ our @options = ( type => $types{alphanum}, category => 'upload', }, - { - name => 'ZM_UPLOAD_FTP_PASS', - default => '', - description => 'Your ftp password', - help => q` - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the password that - ZoneMinder should use to log in for ftp transfer. - `, - requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], - type => $types{string}, - category => 'hidden', - }, { name => 'ZM_UPLOAD_PASS', default => '', @@ -2035,21 +2051,6 @@ our @options = ( type => $types{string}, category => 'upload', }, - { - name => 'ZM_UPLOAD_FTP_LOC_DIR', - default => '@ZM_TMPDIR@', - description => 'The local directory in which to create upload files', - help => q` - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the local directory - that ZoneMinder should use for temporary upload files. These - are files that are created from events, uploaded and then - deleted. - `, - requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], - type => $types{abs_path}, - category => 'hidden', - }, { name => 'ZM_UPLOAD_LOC_DIR', default => '@ZM_TMPDIR@', @@ -2064,19 +2065,6 @@ our @options = ( type => $types{abs_path}, category => 'upload', }, - { - name => 'ZM_UPLOAD_FTP_REM_DIR', - default => '', - description => 'The remote directory to upload to', - help => q` - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the remote directory - that ZoneMinder should use to upload event files to. - `, - requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], - type => $types{rel_path}, - category => 'hidden', - }, { name => 'ZM_UPLOAD_REM_DIR', default => '', @@ -2090,21 +2078,6 @@ our @options = ( type => $types{rel_path}, category => 'upload', }, - { - name => 'ZM_UPLOAD_FTP_TIMEOUT', - default => '120', - description => 'How long to allow the transfer to take for each file', - help => q` - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the maximum ftp - inactivity timeout (in seconds) that should be tolerated before - ZoneMinder determines that the transfer has failed and closes - down the connection. - `, - requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], - type => $types{integer}, - category => 'hidden', - }, { name => 'ZM_UPLOAD_TIMEOUT', default => '120', @@ -2157,20 +2130,6 @@ our @options = ( type => $types{boolean}, category => 'upload', }, - { - name => 'ZM_UPLOAD_FTP_DEBUG', - default => 'no', - description => 'Switch ftp debugging on', - help => q` - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. If you are having (or expecting) troubles - with uploading events then setting this to 'yes' permits - additional information to be included in the zmfilter log file. - `, - requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], - type => $types{boolean}, - category => 'hidden', - }, { name => 'ZM_UPLOAD_DEBUG', default => 'no', @@ -2203,85 +2162,6 @@ our @options = ( type => $types{boolean}, category => 'mail', }, - { - name => 'ZM_EMAIL_ADDRESS', - default => '', - description => 'The email address to send matching event details to', - requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], - help => q` - This option is used to define the email address that any events - that match the appropriate filters will be sent to. - `, - type => $types{email}, - category => 'mail', - }, - { - name => 'ZM_EMAIL_TEXT', - default => 'subject = "ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)" - body = " - Hello, - - An alarm has been detected on your installation of the ZoneMinder. - - The details are as follows :- - - Monitor : %MN% - Event Id : %EI% - Length : %EL% - Frames : %EF% (%EFA%) - Scores : t%EST% m%ESM% a%ESA% - - This alarm was matched by the %FN% filter and can be viewed at %EPS% - - ZoneMinder"', - description => 'The text of the email used to send matching event details', - requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], - help => q` - This option is used to define the content of the email that is - sent for any events that match the appropriate filters. - `, - type => $types{text}, - category => 'hidden', - }, - { - name => 'ZM_EMAIL_SUBJECT', - default => 'ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)', - description => 'The subject of the email used to send matching event details', - requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], - help => q` - This option is used to define the subject of the email that is - sent for any events that match the appropriate filters. - `, - type => $types{string}, - category => 'mail', - }, - { - name => 'ZM_EMAIL_BODY', - default => ' - Hello, - - An alarm has been detected on your installation of the ZoneMinder. - - The details are as follows :- - - Monitor : %MN% - Event Id : %EI% - Length : %EL% - Frames : %EF% (%EFA%) - Scores : t%EST% m%ESM% a%ESA% - - This alarm was matched by the %FN% filter and can be viewed at %EPS% - - ZoneMinder', - description => 'The body of the email used to send matching event details', - requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], - help => q` - This option is used to define the content of the email that is - sent for any events that match the appropriate filters. - `, - type => $types{text}, - category => 'mail', - }, { name => 'ZM_OPT_MESSAGE', default => 'no', @@ -2313,19 +2193,6 @@ our @options = ( type => $types{email}, category => 'mail', }, - { - name => 'ZM_MESSAGE_TEXT', - default => 'subject = "ZoneMinder: Alarm - %MN%-%EI%" - body = "ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score."', - description => 'The text of the message used to send matching event details', - requires => [ { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], - help => q` - This option is used to define the content of the message that - is sent for any events that match the appropriate filters. - `, - type => $types{text}, - category => 'hidden', - }, { name => 'ZM_MESSAGE_SUBJECT', default => 'ZoneMinder: Alarm - %MN%-%EI%', @@ -2428,7 +2295,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 @@ -2618,39 +2485,6 @@ our @options = ( category => 'config', }, # Deprecated, superseded by event close mode - { - name => 'ZM_FORCE_CLOSE_EVENTS', - default => 'no', - description => 'Close events at section ends.', - help => q` - When a monitor is running in a continuous recording mode - (Record or Mocord) events are usually closed after a fixed - period of time (the section length). However in Mocord mode it - is possible that motion detection may occur near the end of a - section and ordinarily this will prevent the event being closed - until the motion has ceased. Switching this option on will - force the event closed at the specified time regardless of any - motion activity. - `, - type => $types{boolean}, - category => 'hidden', - }, - { - name => 'ZM_CREATE_ANALYSIS_IMAGES', - default => 'yes', - description => 'Create analysed alarm images with motion outlined', - help => q` - By default during an alarm ZoneMinder records both the raw - captured image and one that has been analysed and had areas - where motion was detected outlined. This can be very useful - during zone configuration or in analysing why events occurred. - However it also incurs some overhead and in a stable system may - no longer be necessary. This parameter allows you to switch the - generation of these images off. - `, - type => $types{boolean}, - category => 'config', - }, { name => 'ZM_WEIGHTED_ALARM_CENTRES', default => 'no', @@ -2858,37 +2692,9 @@ our @options = ( type => $types{hexadecimal}, category => 'system', }, -# Deprecated, really no longer necessary - { - name => 'ZM_WEB_REFRESH_METHOD', - default => 'javascript', - description => 'What method windows should use to refresh themselves', - help => q` - Many windows in ZoneMinder need to refresh themselves to keep - their information current. This option determines what method - they should use to do this. Choosing 'javascript' means that - each window will have a short JavaScript statement in with a - timer to prompt the refresh. This is the most compatible - method. Choosing 'http' means the refresh instruction is put in - the HTTP header. This is a cleaner method but refreshes are - interrupted or cancelled when a link in the window is clicked - meaning that the window will no longer refresh and this would - have to be done manually. - `, - type => { - db_type =>'string', - hint =>'javascript|http', - pattern =>qr|^([jh])|i, - format =>q( $1 =~ /^j/ - ? 'javascript' - : 'http' - ) - }, - category => 'hidden', - }, { name => 'ZM_WEB_EVENT_SORT_FIELD', - default => 'DateTime', + 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. @@ -2900,7 +2706,7 @@ our @options = ( `, type => { db_type =>'string', - hint =>'Id|Name|Cause|MonitorName|DateTime|Length|Frames|AlarmFrames|TotScore|AvgScore|MaxScore', + hint =>'Id|Name|Cause|DiskSpace|MonitorName|StartDateTime|Length|Frames|AlarmFrames|TotScore|AvgScore|MaxScore', pattern =>qr|.|, format =>q( $1 ) }, @@ -2988,6 +2794,19 @@ our @options = ( requires => [ { name => 'ZM_WEB_LIST_THUMBS', value => 'yes' } ], category => 'web', }, + { + name => 'ZM_WEB_ANIMATE_THUMBS', + default => 'yes', + description => 'Enlarge and show the live stream when a thumbnail is hovered over', + help => q` + Enabling this option causes the static thumbnail, shown on certain + views, to enlarge and show the live stream, when the thumbnail is + hovered over by the mouse. + `, + type => $types{boolean}, + requires => [ { name => 'ZM_WEB_LIST_THUMBS', value => 'yes' } ], + category => 'web', + }, { name => 'ZM_WEB_USE_OBJECT_TAGS', default => 'yes', @@ -3061,7 +2880,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 @@ -3319,7 +3138,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 @@ -3972,7 +3791,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}, @@ -4007,6 +3826,14 @@ our @options = ( type => $types{boolean}, category => 'logging', }, + { + name => 'ZM_FONT_FILE_LOCATION', + default => '@ZM_FONTDIR@/default.zmfnt', + description => 'Font file location', + help => 'This font is used for timestamp labels.', + type => $types{string}, + category => 'config', + }, ); our %options_hash = map { ( $_->{name}, $_ ) } @options; @@ -4025,7 +3852,6 @@ sub initialiseConfig { } else { $option->{value} = ''; } -#next if ( $option->{category} eq 'hidden' ); $option->{id} = $option_id++; } $configInitialised = 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control.pm index 896d10c07..e4d052700 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control.pm @@ -1,6 +1,6 @@ # ========================================================================== # -# ZoneMinder Base Control Module, $Date$, $Revision$ +# ZoneMinder Base Control Module # Copyright (C) 2001-2008 Philip Coombes # # This program is free software; you can redistribute it and/or @@ -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; - my $self = {}; - $self->{name} = $class; - if ( !defined($id) ) { - Fatal('No monitor defined when invoking protocol '.$self->{name}); - } - $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. @@ -116,7 +210,7 @@ sub getParam { } elsif ( defined($default) ) { return $default; } - Fatal("Missing mandatory parameter '$name'"); + Error("Missing mandatory parameter '$name'"); } sub executeCommand { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm index 3fd36a0c4..b95ac9e66 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm @@ -1,16 +1,6 @@ # ========================================================================== # -# ZoneMinder Amcrest HTTP API Control Protocol Module, 20180214, Rev 3.0 -# -# Change Log -# -# Rev 3.0: -# - Fixes incorrect method names -# - Updates control sequences to Amcrest HTTP Protocol API v 2.12 -# - Extends control features -# -# Rev 2.0: -# - Fixed installation instructions text, no changes to functionality. +# ZoneMinder Amcrest HTTP API Control Protocol Module # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -39,6 +29,7 @@ use Time::HiRes qw( usleep ); require ZoneMinder::Base; require ZoneMinder::Control; require LWP::UserAgent; +use URI; our @ISA = qw(ZoneMinder::Control); @@ -63,22 +54,28 @@ sub open { my $self = shift; $self->loadMonitor(); - my $username; - my $password; - my $realm = 'Login to ' . $self->{Monitor}->{ControlDevice}; - - if ( $self->{Monitor}->{ControlAddress} =~ /(.*):(.*)@(.*)/ ) { - $username = $1; - $password = $2; - $$self{address} = $3; + if ( $self->{Monitor}->{ControlAddress} !~ /^\w+:\/\// ) { + # Has no scheme at the beginning, so won't parse as a URI + $self->{Monitor}->{ControlAddress} = 'http://'.$self->{Monitor}->{ControlAddress}; } + my $uri = URI->new($self->{Monitor}->{ControlAddress}); $self->{ua} = LWP::UserAgent->new; - $self->{ua}->credentials($$self{address}, $realm, $username, $password); $self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION); + my ( $username, $password ); + my $realm = 'Login to ' . $self->{Monitor}->{ControlDevice}; + if ( $self->{Monitor}->{ControlAddress} ) { + ( $username, $password ) = $uri->authority() =~ /^(.*):(.*)@(.*)$/; - # Detect REALM - my $res = $self->{ua}->get($$self{address}.'/cgi-bin/ptz.cgi'); + $$self{address} = $uri->host_port(); + $self->{ua}->credentials($uri->host_port(), $realm, $username, $password); + # Testing seems to show that we need the username/password in each url as well as credentials + $$self{base_url} = $uri->canonical(); + Debug('Using initial credentials for '.$uri->host_port().", $realm, $username, $password, base_url: $$self{base_url} auth:".$uri->authority()); + } + + # Detect REALM, has to be /cgi-bin/ptz.cgi because just / accepts no auth + my $res = $self->{ua}->get($$self{base_url}.'cgi-bin/magicBox.cgi?action=getDeviceType'); if ( $res->is_success ) { $self->{state} = 'open'; @@ -94,21 +91,26 @@ sub open { if ( $$headers{'www-authenticate'} ) { my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/; - if ( $tokens =~ /\w+="([^"]+)"/i ) { + if ( $tokens =~ /realm="([^"]+)"/i ) { if ( $realm ne $1 ) { $realm = $1; - Debug("Changing REALM to $realm"); + Debug("Changing REALM to ($realm)"); $self->{ua}->credentials($$self{address}, $realm, $username, $password); - $res = $self->{ua}->get($$self{address}.'/cgi-bin/ptz.cgi'); + $res = $self->{ua}->get($$self{base_url}.'cgi-bin/ptz.cgi'); if ( $res->is_success() ) { $self->{state} = 'open'; return; + } elsif ( $res->status_line eq '400 Bad Request' ) { + # In testing, this second request fails with Bad Request, I assume because we didn't actually give it a command. + $self->{state} = 'open'; + return; + } else { + Error('Authentication still failed after updating REALM' . $res->status_line); + $headers = $res->headers(); + foreach my $k ( keys %$headers ) { + Debug("Header $k => $$headers{$k}"); + } # end foreach } - Error('Authentication still failed after updating REALM' . $res->status_line); - $headers = $res->headers(); - foreach my $k ( keys %$headers ) { - Debug("Initial Header $k => $$headers{$k}"); - } # end foreach } else { Error('Authentication failed, not a REALM problem'); } @@ -118,9 +120,12 @@ sub open { } else { Debug('No headers line'); } # end if headers + } else { + Error("Failed to get $$self{base_url}cgi-bin/magicBox.cgi?action=getDeviceType ".$res->status_line()); + } # end if $res->status_line() eq '401 Unauthorized' - $self->{state} = 'open'; + $self->{state} = 'closed'; } sub close { @@ -135,16 +140,23 @@ sub sendCmd { $self->printMsg($cmd, 'Tx'); - my $req = HTTP::Request->new( GET=>"http://$$self{address}/$cmd" ); - my $res = $self->{ua}->request($req); + my $res = $self->{ua}->get($$self{base_url}.$cmd); if ( $res->is_success ) { $result = !undef; # Command to camera appears successful, write Info item to log - Info('Camera control: \''.$res->status_line().'\' for URL '.$self->{Monitor}->{ControlAddress}.'/'.$cmd); + Info('Camera control: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd); # TODO: Add code to retrieve $res->message_decode or some such. Then we could do things like check the camera status. } else { - Error('Camera control command FAILED: \''.$res->status_line().'\' for URL '.$self->{Monitor}->{ControlAddress}.'/'.$cmd); + # Try again + $res = $self->{ua}->get($$self{base_url}.$cmd); + if ( $res->is_success ) { + # Command to camera appears successful, write Info item to log + Info('Camera control 2: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd); + } else { + Error('Camera control command FAILED: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd); + $res = $self->{ua}->get('http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd); + } } return $result; @@ -175,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" @@ -244,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 @@ -295,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/AxisV2.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/AxisV2.pm index d63236c86..e50449927 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/AxisV2.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/AxisV2.pm @@ -1,6 +1,6 @@ # ========================================================================== # -# ZoneMinder Axis version 2 API Control Protocol Module, $Date$, $Revision$ +# ZoneMinder Axis version 2 API Control Protocol Module # Copyright (C) 2001-2008 Philip Coombes # # This program is free software; you can redistribute it and/or @@ -43,349 +43,380 @@ use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); +use URI; -sub open -{ - my $self = shift; +our $uri; - $self->loadMonitor(); +sub open { + my $self = shift; - use LWP::UserAgent; - $self->{ua} = LWP::UserAgent->new; - $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); + $self->loadMonitor(); + if ($self->{Monitor}->{ControlAddress} and ($self->{Monitor}->{ControlAddress} ne 'user:pass@ip')) { + Debug("Getting connection details from Control Address " . $self->{Monitor}->{ControlAddress}); + if ( $self->{Monitor}->{ControlAddress} !~ /^\w+:\/\// ) { + # Has no scheme at the beginning, so won't parse as a URI + $self->{Monitor}->{ControlAddress} = 'http://'.$self->{Monitor}->{ControlAddress}; + } + $uri = URI->new($self->{Monitor}->{ControlAddress}); + } elsif ($self->{Monitor}->{Path}) { + Debug("Getting connection details from Path " . $self->{Monitor}->{Path}); + $uri = URI->new($self->{Monitor}->{Path}); + $uri->scheme('http'); + $uri->port(80); + $uri->path(''); + } + + use LWP::UserAgent; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->cookie_jar( {} ); + $self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION); + $self->{state} = 'closed'; + + my ( $username, $password, $host ) = ( $uri->authority() =~ /^([^:]+):([^@]*)@(.+)$/ ); + Debug("Have username: $username password: $password host: $host from authority:" . $uri->authority()); + + $uri->userinfo(undef); + + my $realm = $self->{Monitor}->{ControlDevice}; + + $self->{ua}->credentials($uri->host_port(), $realm, $username, $password); + my $url = '/axis-cgi/param.cgi?action=list&group=Properties.PTZ.PTZ'; + + # test auth + my $res = $self->{ua}->get($uri->canonical().$url); + + if ($res->is_success) { + if ($res->content() ne "Properties.PTZ.PTZ=yes\n") { + Warning('Response suggests that camera doesn\'t support PTZ. Content:('.$res->content().')'); + } $self->{state} = 'open'; -} + return; + } + if ($res->status_line() eq '404 Not Found') { + #older style + $url = 'axis-cgi/com/ptz.cgi'; + $res = $self->{ua}->get($uri->canonical().$url); + Debug("Result from getting ".$uri->canonical().$url . ':' . $res->status_line()); + } -sub printMsg -{ - my $self = shift; - my $msg = shift; - my $msg_len = length($msg); - - Debug( $msg."[".$msg_len."]" ); -} - -sub sendCmd -{ - my $self = shift; - my $cmd = shift; - - my $result = undef; - - printMsg( $cmd, "Tx" ); - - #print( "http://$address/$cmd\n" ); - 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()."'" ); + if ($res->status_line() eq '401 Unauthorized') { + my $headers = $res->headers(); + foreach my $k ( keys %$headers ) { + Debug("Initial Header $k => $$headers{$k}"); } - return( $result ); + if ( $$headers{'www-authenticate'} ) { + foreach my $auth_header ( ref $$headers{'www-authenticate'} eq 'ARRAY' ? @{$$headers{'www-authenticate'}} : ($$headers{'www-authenticate'})) { + my ( $auth, $tokens ) = $auth_header =~ /^(\w+)\s+(.*)$/; + if ( $tokens =~ /\w+="([^"]+)"/i ) { + if ( $realm ne $1 ) { + $realm = $1; + $self->{ua}->credentials($uri->host_port(), $realm, $username, $password); + $res = $self->{ua}->get($uri->canonical().$url); + if ( $res->is_success() ) { + Info("Auth succeeded after setting realm to $realm. You can set this value in the Control Device field to speed up connections and remove these log entries."); + $self->{state} = 'open'; + return; + } + Error('Authentication still failed after updating REALM status: '.$res->status_line); + } else { + Error('Authentication failed, not a REALM problem'); + } + } else { + Error('Failed to match realm in tokens'); + } # end if + } # end foreach auth header + } else { + Debug('No headers line'); + } # end if headers + } else { + Debug('Failed to open '.$uri->canonical().$url.' status: '.$res->status_line()); + } # end if $res->status_line() eq '401 Unauthorized' +} # end sub open + +sub sendCmd { + my $self = shift; + my $cmd = shift; + + $self->printMsg($cmd, 'Tx'); + + my $url = $uri->canonical().$cmd; + my $res = $self->{ua}->get($url); + + if ( $res->is_success ) { + Debug('sndCmd command: '.$url.' content: '.$res->content); + return !undef; + } + + Error("Error cmd $url failed: '".$res->status_line()."'"); + + return undef; } -sub cameraReset -{ - my $self = shift; - Debug( "Camera Reset" ); - my $cmd = "/axis-cgi/admin/restart.cgi"; - $self->sendCmd( $cmd ); +sub cameraReset { + my $self = shift; + Debug('Camera Reset'); + my $cmd = '/axis-cgi/admin/restart.cgi'; + $self->sendCmd($cmd); } -sub moveConUp -{ - my $self = shift; - Debug( "Move Up" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=up"; - $self->sendCmd( $cmd ); +sub moveConUp { + my $self = shift; + Debug('Move Up'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=up'; + $self->sendCmd($cmd); } -sub moveConDown -{ - my $self = shift; - Debug( "Move Down" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=down"; - $self->sendCmd( $cmd ); +sub moveConDown { + my $self = shift; + Debug('Move Down'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=down'; + $self->sendCmd($cmd); } -sub moveConLeft -{ - my $self = shift; - Debug( "Move Left" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=left"; - $self->sendCmd( $cmd ); +sub moveConLeft { + my $self = shift; + Debug('Move Left'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=left'; + $self->sendCmd($cmd); } -sub moveConRight -{ - my $self = shift; - Debug( "Move Right" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=right"; - $self->sendCmd( $cmd ); +sub moveConRight { + my $self = shift; + Debug('Move Right'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=right'; + $self->sendCmd($cmd); } -sub moveConUpRight -{ - my $self = shift; - Debug( "Move Up/Right" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=upright"; - $self->sendCmd( $cmd ); +sub moveConUpRight { + my $self = shift; + Debug('Move Up/Right'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=upright'; + $self->sendCmd($cmd); } -sub moveConUpLeft -{ - my $self = shift; - Debug( "Move Up/Left" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=upleft"; - $self->sendCmd( $cmd ); +sub moveConUpLeft { + my $self = shift; + Debug('Move Up/Left'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=upleft'; + $self->sendCmd($cmd); } -sub moveConDownRight -{ - my $self = shift; - Debug( "Move Down/Right" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=downright"; - $self->sendCmd( $cmd ); +sub moveConDownRight { + my $self = shift; + Debug('Move Down/Right'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=downright'; + $self->sendCmd( $cmd ); } -sub moveConDownLeft -{ - my $self = shift; - Debug( "Move Down/Left" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=downleft"; - $self->sendCmd( $cmd ); +sub moveConDownLeft { + my $self = shift; + Debug('Move Down/Left'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=downleft'; + $self->sendCmd($cmd); } -sub moveMap -{ - my $self = shift; - my $params = shift; - my $xcoord = $self->getParam( $params, 'xcoord' ); - my $ycoord = $self->getParam( $params, 'ycoord' ); - Debug( "Move Map to $xcoord,$ycoord" ); - my $cmd = "/axis-cgi/com/ptz.cgi?center=$xcoord,$ycoord&imagewidth=".$self->{Monitor}->{Width}."&imageheight=".$self->{Monitor}->{Height}; - $self->sendCmd( $cmd ); +sub moveMap { + my $self = shift; + my $params = shift; + my $xcoord = $self->getParam($params, 'xcoord'); + my $ycoord = $self->getParam($params, 'ycoord'); + Debug("Move Map to $xcoord,$ycoord"); + my $cmd = "/axis-cgi/com/ptz.cgi?center=$xcoord,$ycoord&imagewidth=".$self->{Monitor}->{Width}.'&imageheight='.$self->{Monitor}->{Height}; + $self->sendCmd($cmd); } -sub moveRelUp -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Up $step" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rtilt=$step"; - $self->sendCmd( $cmd ); +sub moveRelUp { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'tiltstep'); + Debug("Step Up $step"); + my $cmd = '/axis-cgi/com/ptz.cgi?rtilt='.$step; + $self->sendCmd($cmd); } -sub moveRelDown -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Down $step" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rtilt=-$step"; - $self->sendCmd( $cmd ); +sub moveRelDown { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'tiltstep'); + Debug("Step Down $step"); + my $cmd = '/axis-cgi/com/ptz.cgi?rtilt=-'.$step; + $self->sendCmd($cmd); } -sub moveRelLeft -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'panstep' ); - Debug( "Step Left $step" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$step"; - $self->sendCmd( $cmd ); +sub moveRelLeft { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'panstep'); + Debug("Step Left $step"); + my $cmd = '/axis-cgi/com/ptz.cgi?rpan=-'.$step; + $self->sendCmd($cmd); } -sub moveRelRight -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'panstep' ); - Debug( "Step Right $step" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$step"; - $self->sendCmd( $cmd ); +sub moveRelRight { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'panstep'); + Debug("Step Right $step"); + my $cmd = '/axis-cgi/com/ptz.cgi?rpan='.$step; + $self->sendCmd($cmd); } -sub moveRelUpRight -{ - my $self = shift; - my $params = shift; - my $panstep = $self->getParam( $params, 'panstep' ); - my $tiltstep = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Up/Right $tiltstep/$panstep" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$panstep&rtilt=$tiltstep"; - $self->sendCmd( $cmd ); +sub moveRelUpRight { + my $self = shift; + my $params = shift; + my $panstep = $self->getParam($params, 'panstep'); + my $tiltstep = $self->getParam($params, 'tiltstep'); + Debug("Step Up/Right $tiltstep/$panstep"); + my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$panstep&rtilt=$tiltstep"; + $self->sendCmd($cmd); } -sub moveRelUpLeft -{ - my $self = shift; - my $params = shift; - my $panstep = $self->getParam( $params, 'panstep' ); - my $tiltstep = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Up/Left $tiltstep/$panstep" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$panstep&rtilt=$tiltstep"; - $self->sendCmd( $cmd ); +sub moveRelUpLeft { + my $self = shift; + my $params = shift; + my $panstep = $self->getParam($params, 'panstep'); + my $tiltstep = $self->getParam($params, 'tiltstep'); + Debug("Step Up/Left $tiltstep/$panstep"); + my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$panstep&rtilt=$tiltstep"; + $self->sendCmd($cmd); } -sub moveRelDownRight -{ - my $self = shift; - my $params = shift; - my $panstep = $self->getParam( $params, 'panstep' ); - my $tiltstep = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Down/Right $tiltstep/$panstep" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$panstep&rtilt=-$tiltstep"; - $self->sendCmd( $cmd ); +sub moveRelDownRight { + my $self = shift; + my $params = shift; + my $panstep = $self->getParam($params, 'panstep'); + my $tiltstep = $self->getParam($params, 'tiltstep'); + Debug("Step Down/Right $tiltstep/$panstep"); + my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$panstep&rtilt=-$tiltstep"; + $self->sendCmd($cmd); } -sub moveRelDownLeft -{ - my $self = shift; - my $params = shift; - my $panstep = $self->getParam( $params, 'panstep' ); - my $tiltstep = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Down/Left $tiltstep/$panstep" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$panstep&rtilt=-$tiltstep"; - $self->sendCmd( $cmd ); +sub moveRelDownLeft { + my $self = shift; + my $params = shift; + my $panstep = $self->getParam($params, 'panstep'); + my $tiltstep = $self->getParam($params, 'tiltstep'); + Debug("Step Down/Left $tiltstep/$panstep"); + my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$panstep&rtilt=-$tiltstep"; + $self->sendCmd($cmd); } -sub zoomRelTele -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'step' ); - Debug( "Zoom Tele" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=$step"; - $self->sendCmd( $cmd ); +sub zoomRelTele { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'step'); + Debug('Zoom Tele'); + my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=$step"; + $self->sendCmd($cmd); } -sub zoomRelWide -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'step' ); - Debug( "Zoom Wide" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=-$step"; - $self->sendCmd( $cmd ); +sub zoomRelWide { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'step'); + Debug('Zoom Wide'); + my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=-$step"; + $self->sendCmd($cmd); } -sub focusRelNear -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'step' ); - Debug( "Focus Near" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=-$step"; - $self->sendCmd( $cmd ); +sub focusRelNear { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'step'); + Debug('Focus Near'); + my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=-$step"; + $self->sendCmd($cmd); } -sub focusRelFar -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'step' ); - Debug( "Focus Far" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=$step"; - $self->sendCmd( $cmd ); +sub focusRelFar { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'step'); + Debug('Focus Far'); + my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=$step"; + $self->sendCmd($cmd); } -sub focusAuto -{ - my $self = shift; - Debug( "Focus Auto" ); - my $cmd = "/axis-cgi/com/ptz.cgi?autofocus=on"; - $self->sendCmd( $cmd ); +sub focusAuto { + my $self = shift; + Debug('Focus Auto'); + my $cmd = '/axis-cgi/com/ptz.cgi?autofocus=on'; + $self->sendCmd($cmd); } -sub focusMan -{ - my $self = shift; - Debug( "Focus Manual" ); - my $cmd = "/axis-cgi/com/ptz.cgi?autofocus=off"; - $self->sendCmd( $cmd ); +sub focusMan { + my $self = shift; + Debug('Focus Manual'); + my $cmd = '/axis-cgi/com/ptz.cgi?autofocus=off'; + $self->sendCmd($cmd); } -sub irisRelOpen -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'step' ); - Debug( "Iris Open" ); - my $cmd = "/axis-cgi/com/ptz.cgi?riris=$step"; - $self->sendCmd( $cmd ); +sub irisRelOpen { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'step'); + Debug('Iris Open'); + my $cmd = "/axis-cgi/com/ptz.cgi?riris=$step"; + $self->sendCmd($cmd); } -sub irisRelClose -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'step' ); - Debug( "Iris Close" ); - my $cmd = "/axis-cgi/com/ptz.cgi?riris=-$step"; - $self->sendCmd( $cmd ); +sub irisRelClose { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'step'); + Debug('Iris Close'); + my $cmd = "/axis-cgi/com/ptz.cgi?riris=-$step"; + $self->sendCmd($cmd); } -sub irisAuto -{ - my $self = shift; - Debug( "Iris Auto" ); - my $cmd = "/axis-cgi/com/ptz.cgi?autoiris=on"; - $self->sendCmd( $cmd ); +sub irisAuto { + my $self = shift; + Debug('Iris Auto'); + my $cmd = '/axis-cgi/com/ptz.cgi?autoiris=on'; + $self->sendCmd($cmd); } -sub irisMan -{ - my $self = shift; - Debug( "Iris Manual" ); - my $cmd = "/axis-cgi/com/ptz.cgi?autoiris=off"; - $self->sendCmd( $cmd ); +sub irisMan { + my $self = shift; + Debug('Iris Manual'); + my $cmd = '/axis-cgi/com/ptz.cgi?autoiris=off'; + $self->sendCmd($cmd); } -sub presetClear -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Clear Preset $preset" ); - my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset"; - $self->sendCmd( $cmd ); +sub presetClear { + my $self = shift; + my $params = shift; + my $preset = $self->getParam($params, 'preset'); + Debug("Clear Preset $preset"); + my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset"; + $self->sendCmd($cmd); } -sub presetSet -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Set Preset $preset" ); - my $cmd = "/axis-cgi/com/ptz.cgi?setserverpresetno=$preset"; - $self->sendCmd( $cmd ); +sub presetSet { + my $self = shift; + my $params = shift; + my $preset = $self->getParam($params, 'preset'); + Debug("Set Preset $preset"); + my $cmd = "/axis-cgi/com/ptz.cgi?setserverpresetno=$preset"; + $self->sendCmd($cmd); } -sub presetGoto -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Goto Preset $preset" ); - my $cmd = "/axis-cgi/com/ptz.cgi?gotoserverpresetno=$preset"; - $self->sendCmd( $cmd ); +sub presetGoto { + my $self = shift; + my $params = shift; + my $preset = $self->getParam($params, 'preset'); + Debug("Goto Preset $preset"); + my $cmd = "/axis-cgi/com/ptz.cgi?gotoserverpresetno=$preset"; + $self->sendCmd($cmd); } -sub presetHome -{ - my $self = shift; - Debug( "Home Preset" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=home"; - $self->sendCmd( $cmd ); +sub presetHome { + my $self = shift; + Debug('Home Preset'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=home'; + $self->sendCmd($cmd); } 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm new file mode 100644 index 000000000..dd98b828e --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm @@ -0,0 +1,310 @@ +# =========================================================================r +# +# ZoneMinder D-Link DCS-5020L IP Control Protocol Module, $Date: $, $Revision: $ +# Copyright (C) 2013 Art Scheel +# +# 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. +# +# ========================================================================== +# +# This module contains the implementation of the D-Link DCS-5020L IP camera control +# protocol. +# +package ZoneMinder::Control::DCS5020L; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Control; + +our @ISA = qw(ZoneMinder::Control); + +our $VERSION = $ZoneMinder::Base::VERSION; + +# ========================================================================== +# +# D-Link DCS-5020L Control Protocol +# +# ========================================================================== + +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 close { + my $self = shift; + $self->{state} = 'closed'; +} + +sub sendCmd { + my $self = shift; + my $cmd = shift; + my $cgi = shift; + + my $result = undef; + + $self->printMsg($cmd, 'Tx'); + + my $req = HTTP::Request->new(POST=>"http://$self->{Monitor}->{ControlAddress}/$cgi.cgi"); + $req->content($cmd); + my $res = $self->{ua}->request($req); + + if ( $res->is_success ) { + $result = !undef; + } else { + Error("Error check failed: '".$res->status_line()."'"); + } + + return $result; +} + +sub move { + my $self = shift; + my $dir = shift; + my $panStep = shift; + my $tiltStep = shift; + my $cmd = "PanSingleMoveDegree=$panStep&TiltSingleMoveDegree=$tiltStep&PanTiltSingleMove=$dir"; + $self->sendCmd($cmd, 'pantiltcontrol'); +} + +sub moveRel { + my $self = shift; + my $params = shift; + my $panStep = $self->getParam($params, 'panstep', 0); + my $tiltStep = $self->getParam($params, 'tiltstep', 0); + my $dir = shift; + $self->move( $dir, $panStep, $tiltStep ); +} + +sub moveRelUpLeft { + my $self = shift; + my $params = shift; + $self->moveRel($params, 0); +} + +sub moveRelUp { + my $self = shift; + my $params = shift; + $self->moveRel($params, 1); +} + +sub moveRelUpRight { + my $self = shift; + my $params = shift; + $self->moveRel($params, 2); +} + +sub moveRelLeft { + my $self = shift; + my $params = shift; + $self->moveRel($params, 3); +} + +sub moveRelRight { + my $self = shift; + my $params = shift; + $self->moveRel($params, 5); +} + +sub moveRelDownLeft { + my $self = shift; + my $params = shift; + $self->moveRel($params, 6); +} + +sub moveRelDown { + my $self = shift; + my $params = shift; + $self->moveRel($params, 7); +} + +sub moveRelDownRight { + my $self = shift; + my $params = shift; + $self->moveRel($params, 8); +} + +# moves the camera to center on the point that the user clicked on in the video image. +# This isn't extremely accurate but good enough for most purposes +sub moveMap { + # if the camera moves too much or too little, try increasing or decreasing this value + my $f = 11; + + my $self = shift; + my $params = shift; + my $xcoord = $self->getParam( $params, 'xcoord' ); + my $ycoord = $self->getParam( $params, 'ycoord' ); + + my $hor = $xcoord * 100 / $self->{Monitor}->{Width}; + my $ver = $ycoord * 100 / $self->{Monitor}->{Height}; + + my $direction; + my $horSteps; + my $verSteps; + if ($hor < 50 && $ver < 50) { + # up left + $horSteps = (50 - $hor) / $f; + $verSteps = (50 - $ver) / $f; + $direction = 0; + } elsif ($hor >= 50 && $ver < 50) { + # up right + $horSteps = ($hor - 50) / $f; + $verSteps = (50 - $ver) / $f; + $direction = 2; + } elsif ($hor < 50 && $ver >= 50) { + # down left + $horSteps = (50 - $hor) / $f; + $verSteps = ($ver - 50) / $f; + $direction = 6; + } elsif ($hor >= 50 && $ver >= 50) { + # down right + $horSteps = ($hor - 50) / $f; + $verSteps = ($ver - 50) / $f; + $direction = 8; + } + my $v = int($verSteps + .5); + my $h = int($horSteps + .5); + Debug("Move Map to $xcoord,$ycoord, hor=$h, ver=$v with direction $direction"); + $self->move($direction, $h, $v); +} + +sub presetClear { + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Clear Preset $preset" ); + my $cmd = "ClearPosition=$preset"; + $self->sendCmd( $cmd, 'pantiltcontrol' ); +} + +sub presetSet { + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Set Preset $preset" ); + my $cmd = "SetCurrentPosition=$preset&SetName=preset_$preset"; + $self->sendCmd( $cmd, 'pantiltcontrol' ); +} + +sub presetGoto { + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Goto Preset $preset" ); + my $cmd = "PanTiltPresetPositionMove=$preset"; + $self->sendCmd( $cmd, 'pantiltcontrol' ); +} + +sub presetHome { + my $self = shift; + Debug( "Home Preset" ); + $self->move( 4, 0, 0 ); +} + +# IR Controls +# +# wake = IR on +# sleep = IR off +# reset = IR auto + +sub setDayNightMode { + my $self = shift; + my $mode = shift; + my $cmd = "DayNightMode=$mode&ConfigReboot=No"; + $self->sendCmd($cmd, 'daynight'); +} + +sub wake { + my $self = shift; + Debug('Wake - IR on'); + $self->setDayNightMode(2); +} + +sub sleep { + my $self = shift; + Debug('Sleep - IR off'); + $self->setDayNightMode(3); +} + +sub reset { + my $self = shift; + Debug('Reset - IR auto'); + $self->setDayNightMode(0); +} + +1; +__END__ + +=head1 NAME + +ZoneMinder::Control::DCS5020L - Perl extension for DCS-5020L + +=head1 SYNOPSIS + + use ZoneMinder::Database; + DLINK DCS-5020L + +=head1 DESCRIPTION + +ZoneMinder driver for the D-Link consumer camera DCS-5020L. + +=head2 EXPORT + +None by default. + + + +=head1 SEE ALSO + +See if there are better instructions for the DCS-5020L at +https://wiki.zoneminder.com/Dlink + +=head1 AUTHOR + +Art Scheel ascheel (at) gmail + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2018 ZoneMinder LLC + +This library 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 library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU 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. + +=cut 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/HikVision.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm index 8754500fa..8e5b2c9e3 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm @@ -95,9 +95,9 @@ sub PutCmd { my $self = shift; my $cmd = shift; my $content = shift; - my $req = HTTP::Request->new(PUT => "$self->{BaseURL}/$cmd"); + my $req = HTTP::Request->new(PUT => $self->{BaseURL}.'/'.$cmd); if ( defined($content) ) { - $req->content_type("application/x-www-form-urlencoded; charset=UTF-8"); + $req->content_type('application/x-www-form-urlencoded; charset=UTF-8'); $req->content('' . "\n" . $content); } my $res = $self->{UA}->request($req); @@ -135,18 +135,18 @@ sub PutCmd { # Check for username/password # if ( $self->{Monitor}{ControlAddress} =~ /.+:(.+)@.+/ ) { - Info("Check username/password is correct"); + Info('Check username/password is correct'); } elsif ( $self->{Monitor}{ControlAddress} =~ /^[^:]+@.+/ ) { - Info("No password in Control Address. Should there be one?"); + Info('No password in Control Address. Should there be one?'); } elsif ( $self->{Monitor}{ControlAddress} =~ /^:.+@.+/ ) { - Info("Password but no username in Control Address."); + Info('Password but no username in Control Address.'); } else { - Info("Missing username and password in Control Address."); + Info('Missing username and password in Control Address.'); } - Fatal($res->status_line); + Error($res->status_line); } } else { - Fatal($res->status_line); + Error($res->status_line); } } # end unless res->is_success } # end sub putCmd @@ -184,6 +184,7 @@ sub moveVector { # Send it to the camera $self->PutCmd($command,$xml); } +sub zoomStop { $_[0]->moveVector( 0, 0, 0, splice(@_,1)); } sub moveStop { $_[0]->moveVector( 0, 0, 0, splice(@_,1)); } sub moveConUp { $_[0]->moveVector( 0, 1, 0, splice(@_,1)); } sub moveConUpRight { $_[0]->moveVector( 1, 1, 0, splice(@_,1)); } @@ -382,7 +383,7 @@ sub irisRelOpen { sub reset { my $self = shift; - $self->PutCmd("ISAPI/System/reboot"); + $self->PutCmd('ISAPI/System/reboot'); } 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm index 4f6bca07c..8043eedad 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm @@ -122,7 +122,17 @@ sub authentificationHeader { my $nonceBase64 = encode_base64($nonce, ''); my $currentDate = DateTime->now()->iso8601().'Z'; - return '' . $username . '' . digestBase64($nonce, $currentDate, $password) . '' . $nonceBase64 . '' . $currentDate . ''; + return ' + + + + ' . $username . ' + ' . digestBase64($nonce, $currentDate, $password) . ' + ' . $nonceBase64 . ' + ' . $currentDate . ' + + +'; } sub sendCmd { @@ -133,6 +143,7 @@ sub sendCmd { my $result = undef; $self->printMsg($cmd, 'Tx'); + $self->printMsg($msg, 'Tx'); my $server_endpoint = 'http://'.$address.':'.$port.'/'.$cmd; my $req = HTTP::Request->new(POST => $server_endpoint); @@ -147,6 +158,7 @@ sub sendCmd { if ( $res->is_success ) { $result = !undef; +Debug("Result: " . $res->content()); } else { Error("After sending PTZ command, camera returned the following error:'".$res->status_line()."'\nMSG:$msg\nResponse:".$res->content); } @@ -182,7 +194,6 @@ sub getCamParams { } } -#autoStop #This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab sub autoStop { my $self = shift; @@ -190,13 +201,19 @@ sub autoStop { if ( $autostop ) { Debug('Auto Stop'); - my $cmd = 'onvif/PTZ'; - my $msg = '' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '' . $profileToken . 'truefalse'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; usleep($autostop); + + my $cmd = 'onvif/PTZ'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + + my $msg =''.((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '').'' . $profileToken . ''; + $self->sendCmd($cmd, $msg, $content_type); + + # Reported to not work, so superceded by the cmd above + $msg = '' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '' . $profileToken . 'truefalse'; $self->sendCmd($cmd, $msg, $content_type); } -} +} # end sub autoStop # Reset the Camera sub reset { @@ -208,12 +225,56 @@ sub reset { $self->sendCmd($cmd, $msg, $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 = 'onvif/PTZ'; + my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . ' + + ' . $profileToken . ' + + + '; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg, $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 = 'onvif/PTZ'; + my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . ' + + ' . $profileToken . ' + + + + + + '; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg, $content_type); +} + #Up Arrow sub moveConUp { Debug('Move Up'); my $self = shift; my $cmd = 'onvif/PTZ'; - my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '' . $profileToken . ''; + my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . ' + + ' . $profileToken . ' + + '; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg, $content_type); $self->autoStop($self->{Monitor}->{AutoStopTimeout}); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm index ebbaf3a8a..cd2f568de 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm @@ -82,6 +82,13 @@ sub sendCmd { return $result; } +sub reboot { + my $self = shift; + Debug('Camera Rebot'); + my $cmd = '/admin/reboot.cgi?type=0'; + $self->sendCmd($cmd); +} + sub reset { my $self = shift; Debug('Camera Reset'); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm index 84b3c8a14..cc437974f 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; @@ -367,6 +374,25 @@ sub reset { $self->sendCmdPost($url,$cmd); } +sub reboot { + my $self = shift; + Debug('Camera Reboot'); + $self->sendCmdPost('/eng/admin/reboot.cgi', { reboot => 'true' }); + #$referer = 'http://'.$HI->ip().'/eng/admin/tools_default.cgi'; + #$initial_url = $HI->ip().'/eng/admin/tools_default.cgi'; +} + +sub ping { + return -1 if ! $ADDRESS; + + require Net::Ping; + + my $p = Net::Ping->new(); + my $rv = $p->ping($ADDRESS); + $p->close(); + return $rv; +} + 1; __END__ 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 c830d28aa..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; - printMsg($cmd, 'Tx'); + + my $msg = ' + '. + ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . + $msg_body . ' + '; + + $self->printMsg($cmd, 'Tx'); + + my $server_endpoint = $scheme.'://'.$address.':'.$port.$controlUri; + my $req = HTTP::Request->new(POST => $server_endpoint); + $req->header('content-type' => $content_type); + $req->header('Host' => $address . ':' . $port); + $req->header('content-length' => length($msg)); + $req->header('accept-encoding' => 'gzip, deflate'); + $req->header('connection' => 'Close'); + $req->content($msg); + - my $req = HTTP::Request->new(GET=>'http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { - Error("Error check failed:'".$res->status_line()."'" ); + Error("After sending PTZ command, camera returned the following error:'".$res->status_line()."'\nMSG:$msg\nResponse:".$res->content); } - return $result; } sub getCamParams { my $self = shift; + my $msg = ' + + + + 000 + + + '; + + my $server_endpoint = $scheme.'://'.$address.':'.$port.'/onvif/imaging'; + + my $req = HTTP::Request->new(POST => $server_endpoint); + $req->header('content-type' => 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/GetImagingSettings"'); + $req->header('Host' => $address . ':' . $port); + $req->header('content-length' => length($msg)); + $req->header('accept-encoding' => 'gzip, deflate'); + $req->header('connection' => 'Close'); + $req->content($msg); - my $req = HTTP::Request->new(GET=>'http://'.$self->{Monitor}->{ControlAddress}.'/get_camera_params.cgi'); my $res = $self->{ua}->request($req); if ( $res->is_success ) { - # Parse results setting values in %FCParams + # We should really use an xml or soap library to parse the xml tags my $content = $res->decoded_content; - while ($content =~ s/var\s+([^=]+)=([^;]+);//ms) { + if ( $content =~ /.*(.+)<\/tt:Brightness>.*/ ) { + $CamParams{$1} = $2; + } + if ( $content =~ /.*(.+)<\/tt:Contrast>.*/ ) { $CamParams{$1} = $2; } } else { - Error("Error check failed:'".$res->status_line()."'"); + Error("Unable to retrieve camera image settings:'".$res->status_line()."'"); } } @@ -101,256 +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 9a1c79237..ae1259814 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 $@ ) { @@ -146,15 +147,15 @@ sub zmDbGetMonitors { if ( $function ) { if ( $function == DB_MON_CAPT ) { - $sql .= " where Function >= 'Monitor'"; + $sql .= " WHERE `Function` >= 'Monitor'"; } elsif ( $function == DB_MON_ACTIVE ) { - $sql .= " where Function > 'Monitor'"; + $sql .= " WHERE `Function` > 'Monitor'"; } elsif ( $function == DB_MON_MOTION ) { - $sql .= " where Function = 'Modect' or Function = 'Mocord'"; + $sql .= " WHERE `Function` = 'Modect' OR `Function` = 'Mocord'"; } elsif ( $function == DB_MON_RECORD ) { - $sql .= " where Function = 'Record' or Function = 'Mocord'"; + $sql .= " WHERE `Function` = 'Record' OR `Function` = 'Mocord'"; } elsif ( $function == DB_MON_PASSIVE ) { - $sql .= " where Function = 'Nodect'"; + $sql .= " WHERE `Function` = 'Nodect'"; } } my $sth = $dbh->prepare_cached( $sql ); @@ -266,15 +267,17 @@ 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; + } elsif ( ZoneMinder::Logger::logLevel() > INFO ) { + $sql =~ s/\?/'%s'/; + Debug(sprintf("Succeeded $sql : $rows rows affected", @_)); } - return 1; + return $rows; } 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 589e4bbba..026ff6cea 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -32,6 +32,7 @@ require ZoneMinder::Base; require ZoneMinder::Object; require ZoneMinder::Storage; require ZoneMinder::Frame; +require ZoneMinder::Monitor; require Date::Manip; require File::Find; require File::Path; @@ -41,7 +42,8 @@ require Number::Bytes::Human; require Date::Parse; require POSIX; use Date::Format qw(time2str); -use Time::HiRes qw(gettimeofday tv_interval); +use Time::HiRes qw(gettimeofday tv_interval stat); +use Scalar::Util qw(looks_like_number); #our @ISA = qw(ZoneMinder::Object); use parent qw(ZoneMinder::Object); @@ -68,8 +70,8 @@ $serial = $primary_key = 'Id'; SecondaryStorageId Name Cause - StartTime - EndTime + StartDateTime + EndDateTime Width Height Length @@ -111,8 +113,8 @@ sub Time { $_[0]{Time} = $_[1]; } if ( ! defined $_[0]{Time} ) { - if ( $_[0]{StartTime} ) { - $_[0]{Time} = Date::Parse::str2time( $_[0]{StartTime} ); + if ( $_[0]{StartDateTime} ) { + $_[0]{Time} = Date::Parse::str2time( $_[0]{StartDateTime} ); } } return $_[0]{Time}; @@ -349,47 +351,58 @@ sub GenerateVideo { return; } # end sub GenerateVideo +# Note about transactions, this function may be called with rows locked and hence in a transaction. +# So we will detect if we are in a transaction, and if not, start one. We will NOT do rollback or +# commits unless we started the transaction. + sub delete { my $event = $_[0]; + if ( !$event->canEdit() ) { + Warning('No permission to delete event.'); + return 'No permission to delete event.'; + } + my $in_zmaudit = ( $0 =~ 'zmaudit.pl$'); if ( ! $in_zmaudit ) { - if ( ! ( $event->{Id} and $event->{MonitorId} and $event->{StartTime} ) ) { + if ( ! ( $event->{Id} and $event->{MonitorId} and $event->{StartDateTime} ) ) { # zmfilter shouldn't delete anything in an odd situation. zmaudit will though. my ( $caller, undef, $line ) = caller; - Warning("$0 Can't Delete event $event->{Id} from Monitor $event->{MonitorId} StartTime:". - (defined($event->{StartTime})?$event->{StartTime}:'undef')." from $caller:$line"); + Warning("$0 Can't Delete event $event->{Id} from Monitor $event->{MonitorId} StartDateTime:". + (defined($event->{StartDateTime})?$event->{StartDateTime}:'undef')." from $caller:$line"); return; } if ( !($event->Storage()->Path() and -e $event->Storage()->Path()) ) { - Warning('Not deleting event because storage path doesn\'t exist'); + Warning('Not deleting event because storage path ('.$event->Storage()->Path().') doesn\'t exist'); return; } } if ( $$event{Id} ) { # Need to have an event Id if we are to delete from the db. - Info("Deleting event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime} from ".$event->Path()); + Info("Deleting event $event->{Id} from Monitor $event->{MonitorId} StartDateTime:$event->{StartDateTime} from ".$event->Path()); $ZoneMinder::Database::dbh->ping(); - $ZoneMinder::Database::dbh->begin_work(); - #$event->lock_and_load(); + my $in_transaction = $ZoneMinder::Database::dbh->{AutoCommit} ? 0 : 1; - ZoneMinder::Database::zmDbDo('DELETE FROM Frames WHERE EventId=?', $$event{Id}); - if ( $ZoneMinder::Database::dbh->errstr() ) { - $ZoneMinder::Database::dbh->commit(); - return; - } + $ZoneMinder::Database::dbh->begin_work() if ! $in_transaction; + + # Going to delete in order of least value to greatest value. Stats is least and references Frames ZoneMinder::Database::zmDbDo('DELETE FROM Stats WHERE EventId=?', $$event{Id}); if ( $ZoneMinder::Database::dbh->errstr() ) { - $ZoneMinder::Database::dbh->commit(); + $ZoneMinder::Database::dbh->commit() if ! $in_transaction; + return; + } + ZoneMinder::Database::zmDbDo('DELETE FROM Frames WHERE EventId=?', $$event{Id}); + if ( $ZoneMinder::Database::dbh->errstr() ) { + $ZoneMinder::Database::dbh->commit() if ! $in_transaction; return; } # Do it individually to avoid locking up the table for new events ZoneMinder::Database::zmDbDo('DELETE FROM Events WHERE Id=?', $$event{Id}); - $ZoneMinder::Database::dbh->commit(); + $ZoneMinder::Database::dbh->commit() if ! $in_transaction; } if ( ( $in_zmaudit or (!$Config{ZM_OPT_FAST_DELETE})) and $event->Storage()->DoDelete() ) { @@ -402,6 +415,11 @@ sub delete { sub delete_files { my $event = shift; + if ( !$event->canEdit() ) { + Warning('No permission to delete event.'); + return 'No permission to delete event.'; + } + foreach my $Storage ( @_ ? ($_[0]) : ( new ZoneMinder::Storage($$event{StorageId}), @@ -567,9 +585,15 @@ 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 ) = @_; + if ( !$self->canEdit() ) { + Warning('No permission to copy event.'); + return 'No permission to copy event.'; + } + my $OldStorage = $self->Storage(undef); my ( $OldPath ) = ( $self->Path() =~ /^(.*)$/ ); # De-taint if ( ! -e $OldPath ) { @@ -578,7 +602,7 @@ sub CopyTo { # First determine if we can move it to the dest. # We do this before bothering to lock the event my ( $NewPath ) = ( $NewStorage->Path() =~ /^(.*)$/ ); # De-taint - if ( ! $$NewStorage{Id} ) { + if ( ! looks_like_number($$NewStorage{Id}) ) { return 'New storage does not have an id. Moving will not happen.'; } elsif ( $$NewStorage{Id} == $$self{StorageId} ) { return 'Event is already located at ' . $NewPath; @@ -592,16 +616,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.'; } @@ -639,39 +659,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); @@ -682,16 +684,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'; @@ -702,23 +703,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; } @@ -727,29 +721,38 @@ 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) = @_; + + 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; + if (!$self->lock_and_load()) { + Warning('Unable to lock event record '.$$self{Id}); # The fact that we are in a transaction might not imply locking + $ZoneMinder::Database::dbh->commit() if !$was_in_transaction; + return 'Unable to lock event record'; + } - my ( $self, $NewStorage ) = @_; my $OldStorage = $self->Storage(undef); - my $error = $self->CopyTo($NewStorage); + if (!$error) { + # Succeeded in copying all files, so we may now update the Event. + $$self{StorageId} = $$NewStorage{Id}; + $self->Storage($NewStorage); + $error .= $self->save(); + + # 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; - # Succeeded in copying all files, so we may now update the Event. - $$self{StorageId} = $$NewStorage{Id}; - $self->Storage($NewStorage); - $error .= $self->save(); - if ( $error ) { - $ZoneMinder::Database::dbh->commit(); - return $error; - } - $ZoneMinder::Database::dbh->commit(); $self->delete_files($OldStorage); return $error; } # end sub MoveTo @@ -765,44 +768,50 @@ sub recover_timestamps { return; } my @contents = readdir(DIR); - Debug('Have ' . @contents . " files in $path"); + Debug('Have ' . @contents . ' files in '.$path); closedir(DIR); - my @mp4_files = grep( /^\d+\-video\.mp4$/, @contents); + my @mp4_files = grep(/^\d+\-video\.mp4$/, @contents); if ( @mp4_files ) { $$Event{DefaultVideo} = $mp4_files[0]; } - my @analyse_jpgs = grep( /^\d+\-analyse\.jpg$/, @contents); + my @analyse_jpgs = grep(/^\d+\-analyse\.jpg$/, @contents); if ( @analyse_jpgs ) { - $$Event{Save_JPEGs} |= 2; + $$Event{SaveJPEGs} |= 2; } - my @capture_jpgs = grep( /^\d+\-capture\.jpg$/, @contents); + my @capture_jpgs = grep(/^\d+\-capture\.jpg$/, @contents); if ( @capture_jpgs ) { $$Event{Frames} = scalar @capture_jpgs; - $$Event{Save_JPEGs} |= 1; + $$Event{SaveJPEGs} |= 1; # can get start and end times from stat'ing first and last jpg @capture_jpgs = sort { $a cmp $b } @capture_jpgs; my $first_file = "$path/$capture_jpgs[0]"; ( $first_file ) = $first_file =~ /^(.*)$/; my $first_timestamp = (stat($first_file))[9]; - my $last_file = "$path/$capture_jpgs[@capture_jpgs-1]"; + my $last_file = $path.'/'.$capture_jpgs[@capture_jpgs-1]; ( $last_file ) = $last_file =~ /^(.*)$/; my $last_timestamp = (stat($last_file))[9]; my $duration = $last_timestamp - $first_timestamp; $Event->Length($duration); - $Event->StartTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp) ); - $Event->EndTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $last_timestamp) ); - Debug("From capture Jpegs have duration $duration = $last_timestamp - $first_timestamp : $$Event{StartTime} to $$Event{EndTime}"); + $Event->StartDateTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp) ); + if ( $Event->Scheme() eq 'Deep' and $Event->RelativePath(undef) and ($path ne $Event->Path(undef)) ) { + my ( $year, $month, $day, $hour, $minute, $second ) = + ($path =~ /(\d{2})\/(\d{2})\/(\d{2})\/(\d{2})\/(\d{2})\/(\d{2})$/); + Error("Updating starttime to $path $year/$month/$day $hour:$minute:$second"); + $Event->StartDateTime(sprintf('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', 2000+$year, $month, $day, $hour, $minute, $second)); + } + $Event->EndDateTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $last_timestamp) ); + Debug("From capture Jpegs have duration $duration = $last_timestamp - $first_timestamp : $$Event{StartDateTime} to $$Event{EndDateTime}"); $ZoneMinder::Database::dbh->begin_work(); foreach my $jpg ( @capture_jpgs ) { my ( $id ) = $jpg =~ /^(\d+)\-capture\.jpg$/; - if ( ! ZoneMinder::Frame->find_one( EventId=>$$Event{Id}, FrameId=>$id ) ) { - my $file = "$path/$jpg"; + if ( ! ZoneMinder::Frame->find_one(EventId=>$$Event{Id}, FrameId=>$id) ) { + my $file = $path.'/'.$jpg; ( $file ) = $file =~ /^(.*)$/; my $timestamp = (stat($file))[9]; my $Frame = new ZoneMinder::Frame(); @@ -813,11 +822,11 @@ sub recover_timestamps { Type=>'Normal', Score=>0, }); - } - } + } # end if Frame not found + } # end foreach capture jpg $ZoneMinder::Database::dbh->commit(); } elsif ( @mp4_files ) { - my $file = "$path/$mp4_files[0]"; + my $file = $path.'/'.$mp4_files[0]; ( $file ) = $file =~ /^(.*)$/; my $first_timestamp = (stat($file))[9]; @@ -832,8 +841,8 @@ sub recover_timestamps { } my $seconds = ($h*60*60)+($m*60)+$s; $Event->Length($seconds.'.'.$u); - $Event->StartTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp) ); - $Event->EndTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp+$seconds) ); + $Event->StartDateTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp) ); + $Event->EndDateTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp+$seconds) ); } if ( @mp4_files ) { $Event->DefaultVideo($mp4_files[0]); @@ -857,16 +866,41 @@ sub files { sub has_capture_jpegs { @{$_[0]{capture_jpegs}} = grep(/^\d+\-capture\.jpg$/, $_[0]->files()); - Debug("have " . @{$_[0]{capture_jpegs}} . " capture jpegs"); + Debug('have ' . @{$_[0]{capture_jpegs}} . ' capture jpegs'); return @{$_[0]{capture_jpegs}} ? 1 : 0; } sub has_analyse_jpegs { @{$_[0]{analyse_jpegs}} = grep(/^\d+\-analyse\.jpg$/, $_[0]->files()); - Debug("have " . @{$_[0]{analyse_jpegs}} . " analyse jpegs"); + Debug('have ' . @{$_[0]{analyse_jpegs}} . ' analyse jpegs'); return @{$_[0]{analyse_jpegs}} ? 1 : 0; } +sub canEdit { + my $self = shift; + if ( !$ZoneMinder::user ) { + # No user loaded... assume running as system + return 1; + } + if ( !$$ZoneMinder::user{MonitorIds} ) { + # User has no monitor limitations + return 1; + } + if ( $$ZoneMinder::user{Events} eq 'Edit' ) { + return 1; + } + return 0; +} # end sub canEdit + +sub Monitor { + my $self = shift; + $$self{Monitor} = shift if @_; + if ( !$$self{Monitor} ) { + $$self{Monitor} = new ZoneMinder::Monitor($$self{MonitorId}); + } + return $$self{Monitor}; +} + 1; __END__ diff --git a/scripts/ZoneMinder/lib/ZoneMinder/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 6a46ee5d4..726685a7e 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -1,6 +1,6 @@ # ========================================================================== # -# ZoneMinder Filter Module, $Date$, $Revision$ +# ZoneMinder Filter Module # Copyright (C) 2001-2008 Philip Coombes # # This program is free software; you can redistribute it and/or @@ -49,60 +49,57 @@ use ZoneMinder::Database qw(:all); require ZoneMinder::Storage; require ZoneMinder::Server; -sub Name { - if ( @_ > 1 ) { - $_[0]{Name} = $_[1]; - } - return $_[0]{Name}; -} # end sub Path +use vars qw/ $table $primary_key %fields /; +$table = 'Users'; +$primary_key = 'Id'; -sub find { - shift if $_[0] eq 'ZoneMinder::Filter'; - my %sql_filters = @_; - - my $sql = 'SELECT * FROM Filters'; - my @sql_filters; - my @sql_values; - - if ( exists $sql_filters{Name} ) { - push @sql_filters , ' Name = ? '; - push @sql_values, $sql_filters{Name}; - } - - $sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters; - $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; - - my $sth = $ZoneMinder::Database::dbh->prepare_cached($sql) - or Fatal("Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr()); - my $res = $sth->execute(@sql_values) - or Fatal("Can't execute '$sql': ".$sth->errstr()); - - my @results; - - while( my $db_filter = $sth->fetchrow_hashref() ) { - my $filter = new ZoneMinder::Filter($$db_filter{Id}, $db_filter); - push @results, $filter; - } # end while - $sth->finish(); - - return @results; -} - -sub find_one { - my @results = find(@_); - return $results[0] if @results; -} +%fields = map { $_ => $_ } qw( +Id +Name +ExecuteInterval +Query_json +AutoArchive +AutoUnarchive +AutoVideo +AutoUpload +AutoEmail +EmailTo +EmailSubject +EmailBody +AutoMessage +AutoExecute +AutoExecuteCmd +AutoDelete +AutoMove +AutoMoveTo +AutoCopy +AutoCopyTo +UpdateDiskSpace +UserId +Background +Concurrent +LockRows +); sub Execute { my $self = $_[0]; my $sql = $self->Sql(undef); + if ( $$self{PreSQLConditions} and @{$$self{PreSQLConditions}} ) { + foreach my $term ( @{$$self{PreSQLConditions}} ) { + if ( $$term{attr} eq 'DiskPercent' ) { + } + } + } + if ( $self->{HasDiskPercent} ) { - my $disk_percent = getDiskPercent($$self{Storage} ? $$self{Storage}->Path() : ()); + $$self{Storage} = ZoneMinder::Storage->find_one() if ! $$self{Storage}; + my $disk_percent = getDiskPercent($$self{Storage} ? $$self{Storage}->Path() : $Config{ZM_DIR_EVENTS}); $sql =~ s/zmDiskPercent/$disk_percent/g; } if ( $self->{HasDiskBlocks} ) { - my $disk_blocks = getDiskBlocks(); + $$self{Storage} = ZoneMinder::Storage->find_one() if ! $$self{Storage}; + my $disk_blocks = getDiskBlocks($$self{Storage} ? $$self{Storage}->Path() : $Config{ZM_DIR_EVENTS}); $sql =~ s/zmDiskBlocks/$disk_blocks/g; } if ( $self->{HasSystemLoad} ) { @@ -110,6 +107,7 @@ sub Execute { $sql =~ s/zmSystemLoad/$load/g; } + Debug("Filter::Execute SQL ($sql)"); my $sth = $ZoneMinder::Database::dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr()); @@ -119,14 +117,29 @@ sub Execute { return; } my @results; - while ( my $event = $sth->fetchrow_hashref() ) { push @results, $event; } $sth->finish(); - Debug('Loaded ' . @results . " events for filter $_[0]{Name} using query ($sql)"); + Debug('Loaded ' . @results . ' events for filter '.$$self{Name}.' using query ('.$sql.')"'); + if ( $self->{PostSQLConditions} ) { + my @filtered_events; + foreach my $term ( @{$$self{PostSQLConditions}} ) { + if ( $$term{attr} eq 'ExistsInFileSystem' ) { + foreach my $row ( @results ) { + my $event = new ZoneMinder::Event($$row{Id}, $row); + if ( -e $event->Path() ) { + push @filtered_events, $row if $$term{val} eq 'true'; + } else { + push @filtered_events, $row if $$term{val} eq 'false'; + } + } + } + } # end foreach term + @results = @filtered_events; + } # end if has PostSQLConditions return @results; -} +} # end sub Execute sub Sql { my $self = shift; @@ -139,82 +152,89 @@ sub Sql { } my $filter_expr = ZoneMinder::General::jsonDecode($self->{Query_json}); - my $sql = 'SELECT E.*, - unix_timestamp(E.StartTime) as Time, - M.Name as MonitorName, - M.DefaultRate, - M.DefaultScale - FROM Events as E - INNER JOIN Monitors as M on M.Id = E.MonitorId - LEFT JOIN Storage as S on S.Id = E.StorageId - '; + my $sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time + FROM Events as E'; if ( $filter_expr->{terms} ) { foreach my $term ( @{$filter_expr->{terms}} ) { # See getFilterQueryConjunctionTypes() if ( exists($term->{cnj}) and $term->{cnj} =~ /^(and|or)$/ ) { - $self->{Sql} .= ' '.$term->{cnj}.' '; + $self->{Sql} .= ' '.$term->{cnj}; } + $self->{Sql} .= ' '; if ( exists($term->{obr}) ) { - $self->{Sql} .= ' '.str_repeat('(', $term->{obr}).' '; + $self->{Sql} .= str_repeat('(', $term->{obr}).' '; } + 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} =~ /^Monitor/ ) { - my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/; - $self->{Sql} .= 'M.'.$temp_attr_name; - } elsif ( $term->{attr} eq 'ServerId' or $term->{attr} eq 'MonitorServerId' ) { - $self->{Sql} .= 'M.ServerId'; - } elsif ( $term->{attr} eq 'StorageServerId' ) { - $self->{Sql} .= 'S.ServerId'; - } elsif ( $term->{attr} eq 'FilterServerId' ) { - $self->{Sql} .= $Config{ZM_SERVER_ID}; -# StartTime options - } elsif ( $term->{attr} eq 'DateTime' ) { - $self->{Sql} .= 'E.StartTime'; - } elsif ( $term->{attr} eq 'StartDateTime' ) { - $self->{Sql} .= 'E.StartTime'; - } elsif ( $term->{attr} eq 'Date' ) { - $self->{Sql} .= 'to_days( E.StartTime )'; - } elsif ( $term->{attr} eq 'StartDate' ) { - $self->{Sql} .= 'to_days( E.StartTime )'; - } elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' ) { - $self->{Sql} .= 'extract( hour_second from E.StartTime )'; - } elsif ( $term->{attr} eq 'Weekday' or $term->{attr} eq 'StartWeekday' ) { - $self->{Sql} .= 'weekday( E.StartTime )'; -# EndTIme options - } elsif ( $term->{attr} eq 'EndDateTime' ) { - $self->{Sql} .= 'E.EndTime'; - } elsif ( $term->{attr} eq 'EndDate' ) { - $self->{Sql} .= 'to_days( E.EndTime )'; - } elsif ( $term->{attr} eq 'EndTime' ) { - $self->{Sql} .= 'extract( hour_second from E.EndTime )'; - } elsif ( $term->{attr} eq 'EndWeekday' ) { - $self->{Sql} .= "weekday( E.EndTime )"; + 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 )'; -# - } elsif ( $term->{attr} eq 'DiskSpace' ) { - $self->{Sql} .= 'E.DiskSpace'; - $self->{HasDiskPercent} = !undef; - } 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}; - } + # 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 '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} =~ /^MonitorName/ ) { + # Empty value will result in () from split + foreach my $temp_value ( $stripped_value ? split( /["'\s]*?,["'\s]*?/, $stripped_value ) : $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})"); @@ -229,18 +249,19 @@ sub Sql { # 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 'StorageId' ) { + # Empty means NULL, otherwise must be an integer + $value = $temp_value ne '' ? int($temp_value) : 'NULL'; + $$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->{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' ) { @@ -256,6 +277,8 @@ sub Sql { } 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 ) { @@ -280,54 +303,54 @@ sub Sql { } push @value_list, $value; } # end foreach temp_value - } # end if has an attr - if ( $term->{op} ) { - if ( $term->{op} eq '=~' ) { - $self->{Sql} .= " regexp $value"; - } elsif ( $term->{op} eq '!~' ) { - $self->{Sql} .= " not regexp $value"; - } elsif ( $term->{op} eq 'IS' ) { - if ( $value eq 'Odd' ) { - $self->{Sql} .= ' % 2 = 1'; - } elsif ( $value eq 'Even' ) { - $self->{Sql} .= ' % 2 = 0'; + + if ( $term->{op} ) { + if ( $term->{op} eq '=~' ) { + $self->{Sql} .= ' REGEXP '.$value; + } elsif ( $term->{op} eq '!~' ) { + $self->{Sql} .= ' NOT REGEXP '.$value; + } elsif ( $term->{op} eq 'IS' ) { + if ( $value eq 'Odd' ) { + $self->{Sql} .= ' % 2 = 1'; + } elsif ( $value eq 'Even' ) { + $self->{Sql} .= ' % 2 = 0'; + } else { + $self->{Sql} .= ' IS '.$value; + } + } elsif ( $term->{op} eq 'EXISTS' ) { + $self->{Sql} .= ' EXISTS '.$value; + } elsif ( $term->{op} eq 'IS NOT' ) { + $self->{Sql} .= ' IS NOT '.$value; + } elsif ( $term->{op} eq '=[]' or $term->{op} eq 'IN' ) { + $self->{Sql} .= ' IN ('.join(',', @value_list).")"; + } elsif ( $term->{op} eq '![]' ) { + $self->{Sql} .= ' NOT IN ('.join(',', @value_list).')'; + } elsif ( $term->{op} eq 'LIKE' ) { + $self->{Sql} .= ' LIKE '.$value; + } elsif ( $term->{op} eq 'NOT LIKE' ) { + $self->{Sql} .= ' NOT LIKE '.$value; } else { - $self->{Sql} .= " IS $value"; + $self->{Sql} .= ' '.$term->{op}.' '.$value; } - } elsif ( $term->{op} eq 'IS NOT' ) { - $self->{Sql} .= " IS NOT $value"; - } elsif ( $term->{op} eq '=[]' ) { - $self->{Sql} .= ' IN ('.join(',', @value_list).')'; - } elsif ( $term->{op} eq '!~' ) { - $self->{Sql} .= ' NOT IN ('.join(',', @value_list).')'; - } elsif ( $term->{op} eq 'LIKE' ) { - $self->{Sql} .= " LIKE $value"; - } elsif ( $term->{op} eq 'NOT LIKE' ) { - $self->{Sql} .= " NOT LIKE $value"; - } else { - $self->{Sql} .= ' '.$term->{op}.' '.$value; - } - } # end if has an operator - if ( exists($term->{cbr}) ) { - $self->{Sql} .= ' '.str_repeat(')', $term->{cbr}).' '; - } + } # end if has an operator + } # end if Pre/Post or SQL + $self->{Sql} .= ' '.str_repeat(')', $term->{cbr}) if exists($term->{cbr}); + $self->{Sql} .= "\n"; } # end foreach term } # end if terms if ( $self->{Sql} ) { - if ( $self->{AutoMessage} ) { # Include all events, including events that are still ongoing # and have no EndTime yet - $sql .= ' WHERE ( '.$self->{Sql}.' )'; - } else { -# Only include closed events (events with valid EndTime) - $sql .= ' WHERE (E.EndTime IS NOT NULL) AND ( '.$self->{Sql}.' )'; - } + $sql .= ' WHERE ( '.$self->{Sql}.' )'; } my @auto_terms; if ( $self->{AutoArchive} ) { push @auto_terms, 'E.Archived = 0'; } + if ( $self->{AutoUnarchive} ) { + push @auto_terms, 'E.Archived = 1'; + } # Don't do this, it prevents re-generation and concatenation. # If the file already exists, then the video won't be re-recreated if ( $self->{AutoVideo} ) { @@ -348,21 +371,24 @@ sub Sql { if ( @auto_terms ) { $sql .= ' AND ( '.join(' or ', @auto_terms).' )'; } - if ( !$filter_expr->{sort_field} ) { - $filter_expr->{sort_field} = 'StartTime'; - $filter_expr->{sort_asc} = 0; - } + my $sort_column = ''; if ( $filter_expr->{sort_field} eq 'Id' ) { $sort_column = 'E.Id'; } elsif ( $filter_expr->{sort_field} eq 'MonitorName' ) { + $sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName + FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId'; $sort_column = 'M.Name'; } elsif ( $filter_expr->{sort_field} eq 'Name' ) { $sort_column = 'E.Name'; + } elsif ( $filter_expr->{sort_field} eq 'StartDateTime' ) { + $sort_column = 'E.StartDateTime'; } elsif ( $filter_expr->{sort_field} eq 'StartTime' ) { - $sort_column = 'E.StartTime'; + $sort_column = 'E.StartDateTime'; } elsif ( $filter_expr->{sort_field} eq 'EndTime' ) { - $sort_column = 'E.EndTime'; + $sort_column = 'E.EndDateTime'; + } elsif ( $filter_expr->{sort_field} eq 'EndDateTime' ) { + $sort_column = 'E.EndDateTime'; } elsif ( $filter_expr->{sort_field} eq 'Secs' ) { $sort_column = 'E.Length'; } elsif ( $filter_expr->{sort_field} eq 'Frames' ) { @@ -377,14 +403,21 @@ sub Sql { $sort_column = 'E.MaxScore'; } elsif ( $filter_expr->{sort_field} eq 'DiskSpace' ) { $sort_column = 'E.DiskSpace'; - } else { - $sort_column = 'E.StartTime'; + } elsif ( $filter_expr->{sort_field} ne '' ) { + $sort_column = 'E.'.$filter_expr->{sort_field}; } - my $sort_order = $filter_expr->{sort_asc} ? 'ASC' : 'DESC'; - $sql .= ' ORDER BY '.$sort_column." ".$sort_order; - if ( $filter_expr->{limit} ) { + if ( $sort_column ne '' ) { + $sql .= ' ORDER BY '.$sort_column.' '.($filter_expr->{sort_asc} ? 'ASC' : 'DESC'); + } + if ($filter_expr->{limit}) { $sql .= ' LIMIT 0,'.$filter_expr->{limit}; } + if ($$self{LockRows}) { + $sql .= ' FOR UPDATE'; + if ($filter_expr->{skip_locked}) { + $sql .= ' SKIP LOCKED'; + } + } $self->{Sql} = $sql; } # end if has Sql return $self->{Sql}; @@ -401,7 +434,7 @@ sub getDiskPercent { } sub getDiskBlocks { - my $command = 'df .'; + my $command = 'df ' . ($_[0] ? $_[0] : '.'); my $df = qx( $command ); my $space = -1; if ( $df =~ /\s(\d+)\s+\d+\s+\d+%/ms ) { @@ -449,6 +482,15 @@ sub DateTimeToSQL { return POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime($dt_val)); } +sub User { + my $self = shift; + $$self{User} = shift if @_; + if ( ! $$self{User} and $$self{UserId} ) { + $$self{User} = ZoneMinder::User->find_one(Id=>$$self{UserId}); + } + return $$self{User}; +} + 1; __END__ # Below is stub documentation for your module. You'd better edit it! diff --git a/scripts/ZoneMinder/lib/ZoneMinder/General.pm b/scripts/ZoneMinder/lib/ZoneMinder/General.pm index 5b94dea20..90e7399ce 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/General.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/General.pm @@ -28,6 +28,12 @@ our %EXPORT_TAGS = ( makePath jsonEncode jsonDecode + jsonLoad + systemStatus + packageControl + daemonControl + parseNameEqualsValueToHash + hash_diff ) ] ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; @@ -259,21 +265,21 @@ sub createEvent { } $frame->{Type} = $frame->{Score}>0?'Alarm':'Normal' unless( $frame->{Type} ); $frame->{Delta} = $lastTimestamp?($frame->{TimeStamp}-$lastTimestamp):0.0; - $event->{StartTime} = $frame->{TimeStamp} unless ( $event->{StartTime} ); + $event->{StartDateTime} = $frame->{TimeStamp} unless ( $event->{StartDateTime} ); $event->{TotScore} += $frame->{Score}; $event->{MaxScore} = $frame->{Score} if ( $frame->{Score} > $event->{MaxScore} ); $event->{AlarmFrames}++ if ( $frame->{Type} eq 'Alarm' ); - $event->{EndTime} = $frame->{TimeStamp}; + $event->{EndDateTime} = $frame->{TimeStamp}; $lastTimestamp = $frame->{TimeStamp}; } $event->{Width} = $event->{monitor}->{Width} unless( $event->{Width} ); $event->{Height} = $event->{monitor}->{Height} unless( $event->{Height} ); $event->{AvgScore} = $event->{TotScore}/int($event->{AlarmFrames}); - $event->{Length} = $event->{EndTime} - $event->{StartTime}; + $event->{Length} = $event->{EndDateTime} - $event->{StartDateTime}; my %formats = ( - StartTime => 'from_unixtime(?)', - EndTime => 'from_unixtime(?)', + StartDateTime => 'from_unixtime(?)', + EndDateTime => 'from_unixtime(?)', ); my ( @fields, @formats, @values ); @@ -294,7 +300,7 @@ sub createEvent { $event->{Id} = $dbh->{mysql_insertid}; Info( "Created event ".$event->{Id} ); - if ( $event->{EndTime} ) { + if ( $event->{EndDateTime} ) { $event->{Name} = $event->{monitor}->{EventPrefix}.$event->{Id} if ( $event->{Name} eq 'New Event' ); my $sql = "update Events set Name = ? where Id = ?"; @@ -342,7 +348,7 @@ sub createEvent { ." to ".$frame->{capturePath}.": $!" ); setFileOwner( $frame->{capturePath} ); - if ( 0 && $Config{ZM_CREATE_ANALYSIS_IMAGES} ) { + if ( $event->{SaveJPEGs} > 1 ) { $frame->{analysePath} = sprintf( "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS} ."d-analyse.jpg" @@ -380,8 +386,8 @@ sub updateEvent { if ( $event->{Name} eq 'New Event' ); my %formats = ( - StartTime => 'from_unixtime(?)', - EndTime => 'from_unixtime(?)', + StartDateTime => 'from_unixtime(?)', + EndDateTime => 'from_unixtime(?)', ); my ( @values, @sets ); @@ -531,6 +537,91 @@ sub jsonDecode { return $result; } +sub jsonLoad { + my $file = shift; + my $json = undef; + eval { + require File::Slurp; + my $contents = File::Slurp::read_file($file); + if (!$contents) { + Error("No contents for $file"); + return $json; + } + require JSON; + $json = JSON::decode_json($contents); + }; + Error($@) if $@; + return $json; +} + +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; + $string .= ' 2>/dev/null >&- <&- >/dev/null'; + executeShellCommand($string); +} + +sub daemonControl { + my ($command, $daemon, $args) = @_; + my $string = $Config{ZM_PATH_BIN}.'/zmdc.pl '.$command; + if ( $daemon ) { + $string .= ' ' . $daemon; + if ( $args ) { + $string .= ' ' . $args; + } + } + #$string .= ' 2>/dev/null >&- <&- >/dev/null'; + executeShellCommand($string); +} + +sub systemStatus { + my $command = $Config{ZM_PATH_BIN}.'/zmdc.pl check'; + my $output = qx($command); + my $status = $? >> 8; + if ( $status || logDebugging() ) { + $output = '' if !defined($output); + chomp($output); + Debug("Command: $command Output: $output"); + } + return $output; +} + 1; __END__ # Below is stub documentation for your module. You'd better edit it! @@ -542,7 +633,6 @@ ZoneMinder::General - Utility Functions for ZoneMinder =head1 SYNOPSIS use ZoneMinder::General; -blah blah blah =head1 DESCRIPTION @@ -561,6 +651,11 @@ of the ZoneMinder scripts makePath jsonEncode jsonDecode + packageControl + daemonControl + systemStatus + parseNameEqualsValueToHash + hash_diff ) ] diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index 5d05ae7c5..03bc84cd3 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -42,6 +42,15 @@ our @ISA = qw(Exporter ZoneMinder::Base); # will save memory. our %EXPORT_TAGS = ( constants => [ qw( + DEBUG9 + DEBUG8 + DEBUG7 + DEBUG6 + DEBUG5 + DEBUG4 + DEBUG3 + DEBUG2 + DEBUG1 DEBUG INFO WARNING @@ -98,6 +107,15 @@ use Time::HiRes qw/gettimeofday/; use Sys::Syslog; use constant { + DEBUG9 => 9, + DEBUG8 => 8, + DEBUG7 => 7, + DEBUG6 => 6, + DEBUG5 => 5, + DEBUG4 => 4, + DEBUG3 => 3, + DEBUG2 => 2, + DEBUG1 => 1, DEBUG => 1, INFO => 0, WARNING => -1, @@ -108,7 +126,16 @@ use constant { }; our %codes = ( - &DEBUG => 'DBG', + &DEBUG9 => 'DB9', + &DEBUG8 => 'DB8', + &DEBUG7 => 'DB7', + &DEBUG6 => 'DB6', + &DEBUG5 => 'DB5', + &DEBUG4 => 'DB4', + &DEBUG3 => 'DB3', + &DEBUG2 => 'DB2', + &DEBUG1 => 'DB1', + &DEBUG => 'DB1', &INFO => 'INF', &WARNING => 'WAR', &ERROR => 'ERR', @@ -159,7 +186,7 @@ sub new { ( $this->{fileName} = $0 ) =~ s|^.*/||; $this->{logPath} = $ZoneMinder::Config::Config{ZM_PATH_LOGS}; $this->{logFile} = $this->{logPath}.'/'.$this->{id}.'.log'; - ($this->{logFile}) = $this->{logFile} =~ /^([\w\.\/]+)$/; + ($this->{logFile}) = $this->{logFile} =~ /^([_\-\w\.\/]+)$/; $this->{trace} = 0; @@ -210,7 +237,7 @@ sub initialise( @ ) { if ( my $logFile = $this->getTargettedEnv('LOG_FILE') ) { $tempLogFile = $logFile; } - ($tempLogFile) = $tempLogFile =~ /^([\w\.\/]+)$/; + ($tempLogFile) = $tempLogFile =~ /^([_\-\w\.\/]+)$/; my $tempLevel = INFO; my $tempTermLevel = $this->{termLevel}; @@ -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 { @@ -456,9 +483,9 @@ sub fileLevel { if ( defined($fileLevel) ) { $fileLevel = $this->limit($fileLevel); # The filename might have changed, so always close and re-open - $this->closeFile() if ( $this->{fileLevel} > NOLOG ); + $this->closeFile() if $this->{fileLevel} > NOLOG; $this->{fileLevel} = $fileLevel; - $this->openFile() if ( $this->{fileLevel} > NOLOG ); + $this->openFile() if $this->{fileLevel} > NOLOG; } return $this->{fileLevel}; } @@ -490,7 +517,7 @@ sub closeSyslog { sub logFile { my $this = shift; my $logFile = shift; - if ( $logFile =~ /^(.+)\+$/ ) { + if ( $logFile and ( $logFile =~ /^(.+)\+$/ ) ) { $this->{logFile} = $1.'.'.$$; } else { $this->{logFile} = $logFile; @@ -499,13 +526,14 @@ sub logFile { sub openFile { my $this = shift; + if ( open($LOGFILE, '>>', $this->{logFile}) ) { $LOGFILE->autoflush() if $this->{autoFlush}; my $webUid = (getpwnam($ZoneMinder::Config::Config{ZM_WEB_USER}))[2]; - Error("Can't get uid for $ZoneMinder::Config::Config{ZM_WEB_USER}") if ! defined $webUid; + Error('Can\'t get uid for '.$ZoneMinder::Config::Config{ZM_WEB_USER}) if ! defined $webUid; my $webGid = (getgrnam($ZoneMinder::Config::Config{ZM_WEB_GROUP}))[2]; - Error("Can't get gid for $ZoneMinder::Config::Config{ZM_WEB_USER}") if ! defined $webGid; + Error('Can\'t get gid for '.$ZoneMinder::Config::Config{ZM_WEB_USER}) if ! defined $webGid; if ( $> == 0 ) { # If we are root, we want to make sure that www-data or whatever owns the file chown($webUid, $webGid, $this->{logFile} ) or @@ -519,7 +547,6 @@ sub openFile { } sub closeFile { - #my $this = shift; close($LOGFILE) if fileno($LOGFILE); } @@ -610,6 +637,8 @@ sub logInit( ;@ ) { my %options = @_ ? @_ : (); $logger = ZoneMinder::Logger->new() if !$logger; $logger->initialise(%options); + logSetSignal(); + return $logger; } sub logReinit { @@ -626,12 +655,26 @@ sub logHupHandler { $do_log_rotate = 1; } +sub logUSR1Handler { + $logger->level($logger->level()+1); + Info('Logger - Level changed to '. $logger->level() . '=>'.$codes{$logger->level()}); +} + +sub logUSR2Handler { + $logger->level($logger->level()-1); + Info('Logger - Level changed to '. $logger->level() . '=>'.$codes{$logger->level()}); +} + sub logSetSignal { $SIG{HUP} = \&logHupHandler; + $SIG{USR1} = \&logUSR1Handler; + $SIG{USR2} = \&logUSR2Handler; } sub logClearSignal { $SIG{HUP} = 'DEFAULT'; + $SIG{USR1} = 'DEFAULT'; + $SIG{USR2} = 'DEFAULT'; } sub logLevel { @@ -674,38 +717,34 @@ sub Dump { sub debug { my $log = shift; - $log->logPrint(DEBUG, @_, caller); + $log->logPrint(DEBUG1, @_, caller); } -sub Debug( @ ) { - fetch()->logPrint(DEBUG, @_, caller); +sub Debug { + fetch()->logPrint( + (@_ == 1 ? (DEBUG1, @_) : @_), + caller); } -sub Info( @ ) { - fetch()->logPrint(INFO, @_, caller); -} +sub Info { fetch()->logPrint(INFO, @_, caller); } sub info { my $log = shift; $log->logPrint(INFO, @_, caller); } -sub Warning( @ ) { - fetch()->logPrint(WARNING, @_, caller); -} +sub Warning { fetch()->logPrint(WARNING, @_, caller); } sub warn { my $log = shift; $log->logPrint(WARNING, @_, caller); } -sub Error( @ ) { - fetch()->logPrint(ERROR, @_, caller); -} +sub Error { fetch()->logPrint(ERROR, @_, caller); } sub error { my $log = shift; $log->logPrint(ERROR, @_, caller); } -sub Fatal( @ ) { +sub Fatal { my $this = fetch(); $this->logPrint(FATAL, @_, caller); if ( $SIG{TERM} and ( $SIG{TERM} ne 'DEFAULT' ) ) { @@ -720,7 +759,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 53da1659b..e0c9bf971 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in @@ -42,6 +42,7 @@ our @ISA = qw(Exporter ZoneMinder::Base); # will save memory. our %EXPORT_TAGS = ( constants => [ qw( + STATE_UNKNOWN STATE_IDLE STATE_PREALARM STATE_ALARM @@ -98,11 +99,12 @@ our $VERSION = $ZoneMinder::Base::VERSION; use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); -use constant STATE_IDLE => 0; -use constant STATE_PREALARM => 1; -use constant STATE_ALARM => 2; -use constant STATE_ALERT => 3; -use constant STATE_TAPE => 4; +use constant STATE_UNKNOWN => 0; +use constant STATE_IDLE => 1; +use constant STATE_PREALARM => 2; +use constant STATE_ALARM => 3; +use constant STATE_ALERT => 4; +use constant STATE_TAPE => 5; use constant ACTION_GET => 1; use constant ACTION_SET => 2; @@ -144,9 +146,11 @@ our $mem_seq = 0; our $mem_data = { shared_data => { type=>'SharedData', seq=>$mem_seq++, contents=> { size => { type=>'uint32', seq=>$mem_seq++ }, - last_write_index => { type=>'uint32', seq=>$mem_seq++ }, - last_read_index => { type=>'uint32', seq=>$mem_seq++ }, + last_write_index => { type=>'int32', seq=>$mem_seq++ }, + last_read_index => { type=>'int32', seq=>$mem_seq++ }, state => { type=>'uint32', seq=>$mem_seq++ }, + capture_fps => { type=>'double', seq=>$mem_seq++ }, + analysis_fps => { type=>'double', seq=>$mem_seq++ }, last_event => { type=>'uint64', seq=>$mem_seq++ }, action => { type=>'uint32', seq=>$mem_seq++ }, brightness => { type=>'int32', seq=>$mem_seq++ }, @@ -160,12 +164,17 @@ our $mem_data = { signal => { type=>'uint8', seq=>$mem_seq++ }, format => { type=>'uint8', seq=>$mem_seq++ }, imagesize => { type=>'uint32', seq=>$mem_seq++ }, - epadding1 => { type=>'uint32', seq=>$mem_seq++ }, + last_frame_score => { type=>'uint32', seq=>$mem_seq++ }, + audio_frequency => { type=>'uint32', seq=>$mem_seq++ }, + audio_channels => { type=>'uint32', seq=>$mem_seq++ }, startup_time => { type=>'time_t64', seq=>$mem_seq++ }, + zmc_heartbeat_time => { type=>'time_t64', seq=>$mem_seq++ }, 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'=> { @@ -212,7 +221,7 @@ sub zmMemInit { || $member_data->{type} eq 'bool4' ) { $member_data->{size} = $member_data->{align} = 4; - } elsif ($member_data->{type} eq 'int16' + } elsif ( $member_data->{type} eq 'int16' || $member_data->{type} eq 'uint16' ) { $member_data->{size} = $member_data->{align} = 2; @@ -221,6 +230,8 @@ sub zmMemInit { || $member_data->{type} eq 'bool1' ) { $member_data->{size} = $member_data->{align} = 1; + } elsif ( $member_data->{type} eq 'double' ) { + $member_data->{size} = $member_data->{align} = 8; } elsif ( $member_data->{type} =~ /^u?int8\[(\d+)\]$/ ) { $member_data->{size} = $1; $member_data->{align} = 1; @@ -234,7 +245,7 @@ sub zmMemInit { $offset += ($member_data->{align} - ($offset%$member_data->{align})); } $member_data->{offset} = $offset; - $offset += $member_data->{size} + $offset += $member_data->{size}; } $section_data->{size} = $offset - $section_data->{offset}; } @@ -297,11 +308,11 @@ 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; -} +} # end sub zmMemVerify sub zmMemRead { my $monitor = shift; @@ -320,7 +331,7 @@ sub zmMemRead { my $size = $mem_data->{$section}->{contents}->{$element}->{size}; if (!defined $offset || !defined $type || !defined $size) { - Error('Invalid field:'.$field.' setting to undef and exiting zmMemRead'); + Error('Invalid field:'.$field.' setting to undef and exiting zmMemRead offset:'.$offset.' type:'.$type.' size:'.$size); zmMemInvalidate($monitor); return undef; } @@ -353,7 +364,9 @@ sub zmMemRead { } elsif ( $type eq 'int8' ) { ( $value ) = unpack('c', $data); } elsif ( $type eq 'uint8' || $type eq 'bool1' ) { - ( $value ) = unpack( 'C', $data ); + ( $value ) = unpack('C', $data); + } elsif ( $type eq 'double' ) { + ( $value ) = unpack('d', $data); } elsif ( $type =~ /^int8\[\d+\]$/ ) { ( $value ) = unpack('Z'.$size, $data); } elsif ( $type =~ /^uint8\[\d+\]$/ ) { @@ -375,10 +388,8 @@ sub zmMemInvalidate { my $mem_key = zmMemKey($monitor); if ( $mem_key ) { zmMemDetach($monitor); - } else { - Warning('no memkey in zmMemInvalidate'); } -} +} # end sub zmMemInvalidate sub zmMemTidy { zmMemClean(); @@ -504,10 +515,10 @@ sub zmHasAlarmed { my $last_event_id = shift; my ( $state, $last_event ) = zmMemRead($monitor, - ['shared_data:state' ,'shared_data:last_event'] + ['shared_data:state', 'shared_data:last_event'] ); - if ( $state == STATE_ALARM || $state == STATE_ALERT ) { + if ( $state == STATE_ALARM or $state == STATE_ALERT ) { return $last_event; } elsif( $last_event != $last_event_id ) { return $last_event; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm index b9a8b6a1c..d76bbae9a 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm @@ -51,7 +51,7 @@ our %EXPORT_TAGS = ( ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); +our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); our @EXPORT = @EXPORT_OK; @@ -77,17 +77,17 @@ sub zmMemAttach { my ( $monitor, $size ) = @_; if ( !$size ) { - Error("No size passed to zmMemAttach for monitor $$monitor{Id}"); + Error('No size passed to zmMemAttach for monitor '.$$monitor{Id}); return undef; } if ( defined($monitor->{MMapAddr}) ) { - Debug("zmMemAttach already attached at $monitor->{MMapAddr}"); + Debug(3, "zmMemAttach already attached at $monitor->{MMapAddr} for $$monitor{Id}"); return !undef; } my $mmap_file = $Config{ZM_PATH_MAP}.'/zm.mmap.'.$monitor->{Id}; if ( ! -e $mmap_file ) { - Error("Memory map file '$mmap_file' does not exist. zmc might not be running."); + Error("Memory map file '$mmap_file' does not exist in zmMemAttach. zmc might not be running."); return undef; } my $mmap_file_size = -s $mmap_file; @@ -119,18 +119,24 @@ sub zmMemDetach { if ( $monitor->{MMap} ) { if ( ! munmap(${$monitor->{MMap}}) ) { - Warn( "Unable to munmap for monitor $$monitor{Id}\n"); + Warn("Unable to munmap for monitor $$monitor{Id}"); } delete $monitor->{MMap}; + } else { + Warn("No MMap for $$monitor{Id}"); } if ( $monitor->{MMapAddr} ) { delete $monitor->{MMapAddr}; + } else { + Warn("No MMapAddr in $$monitor{Id}"); } if ( $monitor->{MMapHandle} ) { close($monitor->{MMapHandle}); delete $monitor->{MMapHandle}; + } else { + Warn("No MMapHandle in $$monitor{Id}"); } -} +} # end sub zmMemDetach sub zmMemGet { my $monitor = shift; @@ -162,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 47ec7c557..10cfffe8b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -27,18 +27,210 @@ 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); -use vars qw/ $table $primary_key /; +use vars qw/ $table $primary_key %fields $serial %defaults $debug/; $table = 'Monitors'; -$primary_key = 'Id'; +$serial = $primary_key = 'Id'; +%fields = map { $_ => $_ } qw( + Id + Name + Notes + ServerId + StorageId + Type + Function + Enabled + LinkedMonitors + Triggers + EventStartCommand + EventEndCommand + ONVIF_URL + ONVIF_Username + ONVIF_Password + ONVIF_Options + ONVIF_Event_Listener + use_Amcrest_API + Device + Channel + Format + V4LMultiBuffer + V4LCapturesPerFrame + Protocol + Method + Host + Port + SubPath + Path + Options + User + Pass + Width + Height + Colours + Palette + Orientation + Deinterlacing + DecoderHWAccelName + DecoderHWAccelDevice + SaveJPEGs + VideoWriter + OutputCodec + OutputContainer + EncoderParameters + RecordAudio + RTSPDescribe + Brightness + Contrast + Hue + Colour + EventPrefix + LabelFormat + LabelX + LabelY + LabelSize + ImageBufferCount + WarmupCount + PreEventCount + PostEventCount + StreamReplayBuffer + AlarmFrameCount + SectionLength + MinSectionLength + FrameSkip + MotionFrameSkip + AnalysisFPSLimit + AnalysisUpdateDelay + MaxFPS + AlarmMaxFPS + FPSReportInterval + RefBlendPerc + AlarmRefBlendPerc + Controllable + ControlId + ControlDevice + ControlAddress + AutoStopTimeout + TrackMotion + TrackDelay + ReturnLocation + ReturnDelay + ModectDuringPTZ + DefaultRate + DefaultScale + SignalCheckPoints + SignalCheckColour + WebColour + Exif + Sequence + ZoneCount + Refresh + DefaultCodec + Latitude + Longitude + RTSPServer + RTSPStreamName + Importance + ); + +%defaults = ( + ServerId => 0, + StorageId => 0, + Type => q`'Ffmpeg'`, + Function => q`'Mocord'`, + Enabled => 1, + LinkedMonitors => undef, + Device => '', + Channel => 0, + Format => 0, + V4LMultiBuffer => undef, + V4LCapturesPerFrame => 1, + Protocol => undef, + Method => '', + Host => undef, + Port => '', + SubPath => '', + Path => undef, + Options => undef, + User => undef, + Pass => undef, + Width => undef, + Height => undef, + Colours => 4, + Palette => 0, + Orientation => undef, + Deinterlacing => 0, + DecoderHWAccelName => undef, + DecoderHWAccelDevice => undef, + SaveJPEGs => 3, + VideoWriter => 0, + OutputCodec => undef, + OutputContainer => undef, + EncoderParameters => '', + RecordAudio=>0, + RTSPDescribe=>0, + Brightness => -1, + Contrast => -1, + Hue => -1, + Colour => -1, + EventPrefix => q`'Event-'`, + LabelFormat => '', + LabelX => 0, + LabelY => 0, + LabelSize => 1, + ImageBufferCount => 20, + WarmupCount => 0, + PreEventCount => 5, + PostEventCount => 5, + StreamReplayBuffer => 0, + AlarmFrameCount => 1, + SectionLength => 600, + MinSectionLength => 10, + FrameSkip => 0, + MotionFrameSkip => 0, + AnalysisFPSLimit => undef, + AnalysisUpdateDelay => 0, + MaxFPS => undef, + AlarmMaxFPS => undef, + FPSReportInterval => 100, + RefBlendPerc => 6, + AlarmRefBlendPerc => 6, + Controllable => 0, + ControlId => undef, + ControlDevice => undef, + ControlAddress => undef, + AutoStopTimeout => undef, + TrackMotion => 0, + TrackDelay => undef, + ReturnLocation => -1, + ReturnDelay => undef, + ModectDuringPTZ => 0, + DefaultRate => 100, + DefaultScale => 100, + SignalCheckPoints => 0, + SignalCheckColour => q`'#0000BE'`, + WebColour => q`'#ff0000'`, + Exif => 0, + Sequence => undef, + ZoneCount => 0, + Refresh => undef, + DefaultCodec => q`'auto'`, + Latitude => undef, + Longitude => undef, + ); sub Server { return new ZoneMinder::Server( $_[0]{ServerId} ); @@ -48,6 +240,138 @@ sub Storage { return new ZoneMinder::Storage( $_[0]{StorageId} ); } # end sub Storage +sub Zones { + if (! exists $_[0]{Zones}) { + $_[0]{Zones} = [ $_[0]{Id} ? ZoneMinder::Zone->find(MonitorId=>$_[0]{Id}) : () ]; + } + return wantarray ? @{$_[0]{Zones}} : $_[0]{Zones}; +} + +sub control { + my $monitor = shift; + my $command = shift; + my $process = shift; + + if ($command eq 'stop' or $command eq 'restart') { + if ($process) { + ZoneMinder::General::runCommand("zmdc.pl stop $process -m $$monitor{Id}"); + } else { + if ($monitor->{Type} eq 'Local') { + ZoneMinder::General::runCommand('zmdc.pl stop zmc -d '.$monitor->{Device}); + } else { + ZoneMinder::General::runCommand('zmdc.pl stop zmc -m '.$monitor->{Id}); + } + } + } + if ( $command eq 'start' or $command eq 'restart' ) { + if ( $process ) { + ZoneMinder::General::runCommand("zmdc.pl start $process -m $$monitor{Id}"); + } else { + if ($monitor->{Type} eq 'Local') { + ZoneMinder::General::runCommand('zmdc.pl start zmc -d '.$monitor->{Device}); + } else { + ZoneMinder::General::runCommand('zmdc.pl start zmc -m '.$monitor->{Id}); + } + } # end if + } +} # end sub control + +sub Status { + my $self = shift; + $$self{Status} = shift if @_; + if ( ! $$self{Status} ) { + $$self{Status} = ZoneMinder::Monitor_Status->find_one(MonitorId=>$$self{Id}); + } + return $$self{Status}; +} + +sub 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}) { + if ($$self{ControlId}) { + require ZoneMinder::Control; + my $Control = ZoneMinder::Control->find_one(Id=>$$self{ControlId}); + if ($Control) { + my $Protocol = $$Control{Protocol}; + + if (!$Protocol) { + Error("No protocol set in control $$Control{Id}, trying Name $$Control{Name}"); + $Protocol = $$Control{Name}; + } + require Module::Load::Conditional; + if (!Module::Load::Conditional::can_load(modules => {'ZoneMinder::Control::'.$Protocol => undef})) { + Error("Can't load ZoneMinder::Control::$Protocol\n$Module::Load::Conditional::ERROR"); + return undef; + } + bless $Control, 'ZoneMinder::Control::'.$Protocol; + $$Control{MonitorId} = $$self{Id}; + $$self{Control} = $Control; + } else { + Error("Unable to load control for control $$self{ControlId} for monitor $$self{Id}"); + } + } else { + Info("No ControlId set in monitor $$self{Id}") + } + } + return $$self{Control}; +} + 1; __END__ diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm new file mode 100644 index 000000000..1fafd3b0b --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm @@ -0,0 +1,83 @@ +# ========================================================================== +# +# ZoneMinder Monitor_STatus Module +# Copyright (C) 2020 ZoneMinder +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# ========================================================================== +# +# This module contains the common definitions and functions used by the rest +# of the ZoneMinder scripts +# +package ZoneMinder::Monitor_Status; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Object; + +#our @ISA = qw(Exporter ZoneMinder::Base); +use parent qw(ZoneMinder::Object); + +use vars qw/ $table $primary_key %fields $serial %defaults $debug/; +$table = 'Monitor_Status'; +$serial = $primary_key = 'MonitorId'; +%fields = map { $_ => $_ } qw( + MonitorId + Status + CaptureFPS + AnalysisFPS + CaptureBandwidth + ); + +%defaults = ( + Status => 'Unknown', + CaptureFPS => undef, + AnalysisFPS => undef, + CaptureBandwidth => undef, + ); + +sub Monitor { + return new ZoneMinder::Monitor( $_[0]{MonitorId} ); +} # end sub Monitor + +1; +__END__ + +=head1 NAME + +ZoneMinder::Monitor_Status - Perl Class for Monitor Status + +=head1 SYNOPSIS + +use ZoneMinder::Monitor_Status; + +=head1 AUTHOR + +Isaac Connor, Eisaac@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2017 ZoneMinder LLC + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in index f6863367d..d90a5fea3 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in @@ -39,12 +39,10 @@ our %EXPORT_TAGS = ( ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); +our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); our @EXPORT = qw(); -our $VERSION = $ZoneMinder::Base::VERSION; - use Data::UUID; use vars qw( $verbose $soap_version ); @@ -60,13 +58,16 @@ require WSDiscovery::TransportUDP; sub deserialize_message { my ($wsdl_client, $response) = @_; + if ( ! $response ) { + return; + } # copied and adapted from SOAP::WSDL::Client # get deserializer my $deserializer = $wsdl_client->get_deserializer(); - if(! $deserializer) { + if ( !$deserializer ) { $deserializer = SOAP::WSDL::Factory::Deserializer->get_deserializer({ soap_version => $wsdl_client->get_soap_version(), %{ $wsdl_client->get_deserializer_args() }, @@ -74,22 +75,18 @@ sub deserialize_message { } # set class resolver if serializer supports it $deserializer->set_class_resolver( $wsdl_client->get_class_resolver() ) - if ( $deserializer->can('set_class_resolver') ); + if $deserializer->can('set_class_resolver'); # Try deserializing response - there may be some, -# even if transport did not succeed (got a 500 response) - if ( ! $response ) { - return; - } - +# even if transport did not succeed (got a 500 response) # as our faults are false, returning a success marker is the only # reliable way of determining whether the deserializer succeeded. # Custom deserializers may return an empty list, or undef, # and $@ is not guaranteed to be undefined. my ($success, $result_body, $result_header) = eval { - (1, $deserializer->deserialize( $response )); + (1, $deserializer->deserialize($response)); }; - if (defined $success) { + if ( defined $success ) { return wantarray ? ($result_body, $result_header) : $result_body; @@ -97,7 +94,6 @@ sub deserialize_message { return $@; } - #else return $deserializer->generate_fault({ code => 'soap:Server', role => 'urn:localhost', @@ -107,14 +103,11 @@ sub deserialize_message { } # end sub deserialize_message sub interpret_messages { - my ($svc_discover, $services, @responses ) = @_; + my ( $svc_discover, $services, @responses ) = @_; my @results; foreach my $response ( @responses ) { - - if ( $verbose ) { - print "Received message:\n" . $response . "\n"; - } + print "Received message:\n" . $response . "\n" if $verbose; my $result = deserialize_message($svc_discover, $response); if ( not $result ) { @@ -143,28 +136,26 @@ sub interpret_messages { next if defined $services->{$xaddr}; $services->{$xaddr} = 1; - print "$xaddr, " . $svc_discover->get_soap_version() . ", "; + print $xaddr.', '.$svc_discover->get_soap_version().', '; - print "("; + print '('; my $scopes = $result->get_ProbeMatch()->get_Scopes(); my $count = 0; my %scopes; - foreach my $scope(split ' ', $scopes) { - if($scope =~ m|onvif://www\.onvif\.org/(.+)/(.*)|) { + foreach my $scope (split ' ', $scopes) { + if ( $scope =~ m|onvif://www\.onvif\.org/(.+)/(.*)| ) { my ($attr, $value) = ($1,$2); - if( 0 < $count ++) { - print ", "; - } - print $attr . "=\'" . $value . "\'"; + print ', ' if 0 < $count ++; + print $attr . '=\'' . $value . '\''; $scopes{$attr} = $value; } } print ")\n"; - push @results, { xaddr=>$xaddr, + push @results, { + xaddr => $xaddr, soap_version => $svc_discover->get_soap_version(), - scopes => \%scopes, + scopes => \%scopes, }; - } return @results; } # end sub interpret_messages @@ -172,7 +163,7 @@ sub interpret_messages { # functions sub discover { - my ( $soap_version ) = @_; + my ($soap_versions, $net_interface) = @_; my @results; ## collect all responses @@ -185,27 +176,36 @@ sub discover { push @responses, $response; }; -## try both soap versions - my $uuid_gen = Data::UUID->new(); - if ( ( ! $soap_version ) or ( $soap_version eq '1.1' ) ) { - my %services; + foreach my $version ( $soap_versions ? ( split(',',$soap_versions) ) : ( '1.1', '1.2') ) { + my %services; - if($verbose) { - print "Probing for SOAP 1.1\n" - } + print "Probing for SOAP $version\n" if $verbose; my $svc_discover = WSDiscovery10::Interfaces::WSDiscovery::WSDiscoveryPort->new({ # no_dispatch => '1', }); - $svc_discover->set_soap_version('1.1'); + $svc_discover->set_soap_version($version); + if ( $net_interface ) { + my $transport = $svc_discover->get_transport(); + print "Setting net interface for $transport to $net_interface\n" if $verbose; + $transport->set_net_interface($net_interface); + } my $uuid = $uuid_gen->create_str(); my $result = $svc_discover->ProbeOp( - { # WSDiscovery::Types::ProbeType - Types => 'http://www.onvif.org/ver10/network/wsdl:NetworkVideoTransmitter http://www.onvif.org/ver10/device/wsdl:Device', # QNameListType - Scopes => { value => '' }, + { # WSDiscovery::Types::ProbeType + ( + ($version eq '1.1') ? + ( + Types => 'http://www.onvif.org/ver10/network/wsdl:NetworkVideoTransmitter http://www.onvif.org/ver10/device/wsdl:Device', # QNameListType + ) : ( + xmlattr => { 'xmlns:dn' => 'http://www.onvif.org/ver10/network/wsdl', }, + ) + ), + Types => 'dn:NetworkVideoTransmitter', # QNameListType + Scopes => { value => '' }, }, WSDiscovery10::Elements::Header->new({ Action => { value => 'http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe' }, @@ -213,107 +213,123 @@ sub discover { To => { value => 'urn:schemas-xmlsoap-org:ws:2005:04:discovery' }, }) ); - print $result . "\n" if $verbose; + print $result."\n" if $verbose; push @results, interpret_messages($svc_discover, \%services, @responses); - @responses = (); - } # end if doing soap 1.1 + @responses = (); + } # end foreach version - if ( ( ! $soap_version ) or ( $soap_version eq '1.2' ) ) { - my %services; - if($verbose) { - print "Probing for SOAP 1.2\n" - } - my $svc_discover = WSDiscovery10::Interfaces::WSDiscovery::WSDiscoveryPort->new({ -# no_dispatch => '1', - }); - $svc_discover->set_soap_version('1.2'); - -# copies of the same Probe message must have the same MessageID. -# This is not a copy. So we generate a new uuid. - my $uuid = $uuid_gen->create_str(); - -# Everyone else, like the nodejs onvif code and odm only ask for NetworkVideoTransmitter - my $result = $svc_discover->ProbeOp( - { # WSDiscovery::Types::ProbeType - xmlattr => { 'xmlns:dn' => 'http://www.onvif.org/ver10/network/wsdl', }, - Types => 'dn:NetworkVideoTransmitter', # QNameListType - Scopes => { value => '' }, - }, - WSDiscovery10::Elements::Header->new({ - Action => { value => 'http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe' }, - MessageID => { value => "urn:uuid:$uuid" }, - To => { value => 'urn:schemas-xmlsoap-org:ws:2005:04:discovery' }, - }) - ); - print $result . "\n" if $verbose; - push @results, interpret_messages($svc_discover, \%services, @responses); - } # end if doing soap 1.2 return @results; -} +} # end sub discover sub profiles { - my ( $client ) = @_; + my ($client) = @_; - my $endpoint = $client->get_endpoint('media'); - if ( ! $endpoint ) { - print "No media enpoint for client.\n"; + my $media = $client->get_endpoint('media'); + if ( !$media ) { + print "No media endpoint for client.\n"; return; } - my $result = $endpoint->GetProfiles( { } ,, ); - if ( ! $result ) { - print "No result from GetProfiles\n"; + my $result = $media->GetProfiles( { } ,, ); + if ( !$result ) { + print "No result from GetProfiles.\n"; return; } if ( $verbose ) { - print "Received message:\n" . $result . "\n"; + use XML::LibXML; + my $dom = XML::LibXML->load_xml(string=>$result); + print "Received message:\n" . $dom->toString(1) . "\n"; } + my @Profiles = @{ $result->get_Profiles() }; + if ( !@Profiles ) { + print "No profiles returned from get_Profiles\n"; + return; + } + print 'Number of profiles found: ' .(scalar @Profiles)."\n" if $verbose; + my @profiles; - my $profiles = $result->get_Profiles(); - - foreach my $profile ( @{ $profiles } ) { - + foreach my $profile ( @Profiles ) { my $token = $profile->attr()->get_token() ; - my $video_encoder_configuration = $profile->get_VideoEncoderConfiguration(); - if ( ! $video_encoder_configuration ) { - print "Unknown profile $token " . $profile->get_Name()."\n"; + my $Name = $profile->get_Name(); + + my $VideoEncoderConfiguration = $profile->get_VideoEncoderConfiguration(); + if ( ! $VideoEncoderConfiguration ) { + print "No VideoEncoderConfiguration in profile $token $Name.\n"; next; } - print $token . ", " . - $profile->get_Name() . ", " . - $profile->get_VideoEncoderConfiguration()->get_Encoding() . ", " . - $profile->get_VideoEncoderConfiguration()->get_Resolution()->get_Width() . ", " . - $profile->get_VideoEncoderConfiguration()->get_Resolution()->get_Height() . ", " . - $profile->get_VideoEncoderConfiguration()->get_RateControl()->get_FrameRateLimit() . - ", "; # Specification gives conflicting values for unicast stream types, try both. # http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl#op.GetStreamUri - foreach my $streamtype ( 'RTP_unicast', 'RTP-Unicast' ) { - $result = $client->get_endpoint('media')->GetStreamUri( { + foreach my $streamtype ( 'RTP_unicast', 'RTP-Unicast', 'RTP-multicast', 'RTP-Multicast' ) { + my $StreamUri = $media->GetStreamUri( { StreamSetup => { # ONVIF::Media::Types::StreamSetup - Stream => $streamtype, # StreamType - Transport => { # ONVIF::Media::Types::Transport - Protocol => 'RTSP', # TransportProtocol - }, + Stream => $streamtype, # StreamType + Transport => { # ONVIF::Media::Types::Transport + Protocol => 'RTSP', # TransportProtocol + }, }, ProfileToken => $token, # ReferenceToken - } ,, ); - last if $result; - } - die $result if not $result; -# print $result . "\n"; + } ); + if ( ! ( $StreamUri and $StreamUri->can('get_MediaUri') ) ) { + print "No StreamUri or no MediaUri on profile $Name of type $streamtype\n" if $verbose; + next; + } + if ( $verbose ) { + eval { + use XML::LibXML; + my $dom = XML::LibXML->load_xml(string=>$StreamUri); + print "Received message:\n" . $dom->toString(1) . "\n"; + }; + } + my $MediaUri = $StreamUri->get_MediaUri(); + if ( !$MediaUri ) { + print "No MediaUri in profile $Name of type $streamtype\n"; + next; + } + if ( $verbose ) { + eval { + use XML::LibXML; + my $dom = XML::LibXML->load_xml(string=>$MediaUri); + print "Received message:\n" . $dom->toString(1) . "\n"; + }; + } + my $Uri = $MediaUri->get_Uri(); + if ( ! $Uri ) { + print "No Uri in profile $Name of type $streamtype\n"; + next; + } + if ( $verbose ) { + eval { + use XML::LibXML; + my $dom = XML::LibXML->load_xml(string=>$Uri); + print "Received message:\n" . $dom->toString(1) . "\n"; + }; + } + my $Resolution = $VideoEncoderConfiguration->get_Resolution(); + my $Width = $Resolution ? $Resolution->get_Width() : 0; + my $Height = $Resolution ? $Resolution->get_Height() : 0; + + push @profiles, [ + $token, + $Name, + $VideoEncoderConfiguration->get_Encoding(), + $Width, + $Height, + $VideoEncoderConfiguration->get_RateControl()->get_FrameRateLimit(), + $streamtype, + $Uri, + ]; + } # end foreach streamtype - print $result->get_MediaUri()->get_Uri() . - "\n"; } # end foreach profile # # use message parser without schema validation ??? # + return @profiles; -} +} # end sub profiles sub move { my ($client, $dir) = @_; @@ -325,14 +341,23 @@ sub move { } # end sub move sub metadata { - my ( $client ) = @_; - my $result = $client->get_endpoint('media')->GetMetadataConfigurations( { } ,, ); - die $result if not $result; - print $result . "\n"; + my ($client) = @_; + my $media = $client->get_endpoint('media'); + die 'No media endpoint.' if !$media; - $result = $client->get_endpoint('media')->GetVideoAnalyticsConfigurations( { } ,, ); - die $result if not $result; - print $result . "\n"; + my $result = $media->GetMetadataConfigurations( { } ,, ); + if ( !$result ) { + print "No MetaDataConfigurations\n" if $verbose; + } else { + print $result . "\n"; + } + + $result = $media->GetVideoAnalyticsConfigurations( { } ,, ); + if ( ! $result ) { + print "No VideoAnalyticsConfigurations\n" if $verbose; + } else { + print $result . "\n"; + } # $result = $client->get_endpoint('analytics')->GetServiceCapabilities( { } ,, ); # die $result if not $result; @@ -340,8 +365,6 @@ sub metadata { } - - 1; __END__ diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm index 569b3de9d..d64ee7e8a 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm @@ -22,6 +22,12 @@ # This module contains the common definitions and functions used by the rest # of the ZoneMinder scripts # + +sub array_diff(\@\@) { + my %e = map { $_ => undef } @{$_[1]}; + return @{[ ( grep { (exists $e{$_}) ? ( delete $e{$_} ) : ( 1 ) } @{ $_[0] } ), keys %e ] }; +} + package ZoneMinder::Object; use 5.006; @@ -40,6 +46,10 @@ our @ISA = qw(ZoneMinder::Base); # # ========================================================================== +sub def_or_undef { + return defined($_[0]) ? $_[0] : 'undef'; +} + use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); @@ -208,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'; @@ -224,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"); @@ -257,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 ) { @@ -272,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 ) { @@ -311,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 ) { @@ -330,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 ) { @@ -340,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}} ) { @@ -370,40 +380,29 @@ sub set { $log->error("$type -> set called with non-hash params from $caller $line"); } - foreach my $field ( keys %fields ) { - if ( $params ) { - $log->debug("field: $field, param: ".$$params{$field}) if $debug; - if ( exists $$params{$field} ) { - $log->debug("field: $field, $$self{$field} =? param: ".$$params{$field}) if $debug; - if ( ( ! defined $$self{$field} ) or ($$self{$field} ne $params->{$field}) ) { -# Only make changes to fields that have changed - if ( defined $fields{$field} ) { - $$self{$field} = $$params{$field} if defined $fields{$field}; - push @set_fields, $fields{$field}, $$params{$field}; #mark for sql updating - } # end if - $log->debug("Running $field with $$params{$field}") if $debug; - if ( my $func = $self->can( $field ) ) { - $func->( $self, $$params{$field} ); - } # end if - } # end if - } # end if - } # end if $params - - if ( defined $fields{$field} ) { - if ( $$self{$field} ) { - $$self{$field} = transform( $type, $field, $$self{$field} ); - } # end if $$self{field} - } - } # end foreach field + if ( $params ) { + foreach my $field ( keys %{$params} ) { + $log->debug("field: $field, ".def_or_undef($$self{$field}).' =? param: '.def_or_undef($$params{$field})) if $debug; + if ( ( ! defined $$self{$field} ) or ($$self{$field} ne $params->{$field}) ) { + # Only make changes to fields that have changed + if ( defined $fields{$field} ) { + $$self{$field} = $$params{$field}; + push @set_fields, $fields{$field}, $$params{$field}; #mark for sql updating + } # end if has a column + $log->debug("Running $field with $$params{$field}") if $debug; + if ( my $func = $self->can( $field ) ) { + $func->( $self, $$params{$field} ); + } # end if has function + } # end if has change + } # end foreach field + } # end if $params foreach my $field ( keys %defaults ) { - if ( ( ! exists $$self{$field} ) or (!defined $$self{$field}) or ( $$self{$field} eq '' ) ) { - $log->debug("Setting default ($field) ($$self{$field}) ($defaults{$field}) ") if $debug; + $log->debug("Setting default ($field) (".def_or_undef($$self{$field}).') ('.def_or_undef($defaults{$field}).') ') if $debug; if ( defined $defaults{$field} ) { - $log->debug("Default $field is defined: $defaults{$field}") if $debug; - if ( $defaults{$field} eq 'NOW()' ) { - $$self{$field} = 'NOW()'; + if ( $defaults{$field} eq '' or $defaults{$field} eq 'NOW()' ) { + $$self{$field} = $defaults{$field}; } else { $$self{$field} = eval($defaults{$field}); $log->error( "Eval error of object default $field default ($defaults{$field}) Reason: " . $@ ) if $@; @@ -411,8 +410,9 @@ sub set { } else { $$self{$field} = $defaults{$field}; } # end if -#$$self{$field} = ( defined $defaults{$field} ) ? eval($defaults{$field}) : $defaults{$field}; - $log->debug("Setting default for ($field) using ($defaults{$field}) to ($$self{$field}) ") if $debug; + $log->debug("Setting default for ($field) using (".def_or_undef($defaults{$field}).') to ('.def_or_undef($$self{$field}).') ') if $debug; + } elsif ( defined $fields{$field} and $$self{$field} ) { + $$self{$field} = transform( $type, $field, $$self{$field} ); } # end if } # end foreach default return @set_fields; @@ -639,9 +639,9 @@ $log->debug("Have array for $k $$search{$k}") if DEBUG_ALL; if ( ! ( $db_field =~ /\?/ ) ) { if ( @{$$search{$k}} != 1 ) { - push @where, $db_field .' IN ('.join(',', map {'?'} @{$$search{$k}} ) . ')'; + push @where, '`'.$db_field .'` IN ('.join(',', map {'?'} @{$$search{$k}} ) . ')'; } else { - push @where, $db_field.'=?'; + push @where, '`'.$db_field.'`=?'; } # end if } else { $log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL; @@ -656,10 +656,10 @@ $log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL; foreach my $p_k ( keys %{$$search{$k}} ) { my $v = $$search{$k}{$p_k}; if ( ref $v eq 'ARRAY' ) { - push @where, $db_field.' IN ('.join(',', map {'?'} @{$v} ) . ')'; + push @where, '`'.$db_field.'` IN ('.join(',', map {'?'} @{$v} ) . ')'; push @values, $p_k, @{$v}; } else { - push @where, $db_field.'=?'; + push @where, '`'.$db_field.'`=?'; push @values, $p_k, $v; } # end if } # end foreach p_k @@ -667,7 +667,7 @@ $log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL; push @where, $db_field.' IS NULL'; } else { if ( ! ( $db_field =~ /\?/ ) ) { - push @where, $db_field .'=?'; + push @where, '`'.$db_field .'`=?'; } else { push @where, $db_field; } @@ -852,6 +852,42 @@ sub find_sql { return \%sql; } # end sub find_sql +sub changes { + my ( $self, $params ) = @_; + + my $type = ref $self; + if ( ! $type ) { + my ( $caller, undef, $line ) = caller; + $log->error("No type in Object::changes. self:$self from $caller:$line"); + } + my $fields = eval ('\%'.$type.'::fields'); + if (!$fields) { + $log->warn('Object::changes called on an object with no fields'); + return; + } # end if + my @results; + + foreach my $field (sort keys %$fields) { + next if ! exists $$params{$field}; + + if ( ref $$self{$field} eq 'ARRAY' ) { + my @second = ref $$params{$field} eq 'ARRAY' ? @{$$params{$field}} : ($$params{$field}); + if (array_diff(@{$$self{$field}}, @second)) { + push @results, [ $field, $$self{$field}, $$params{$field} ]; + } + } elsif ( + (!defined($$self{$field}) and defined($$params{$field})) + or + (defined($$self{$field}) and !defined($$params{$field})) + ) { + push @results, $field; + } elsif ( defined($$self{$field}) and defined($$params{$field}) and ($$self{$field} ne $$params{$field}) ) { + push @results, $field; + } + } + return @results; +} + sub AUTOLOAD { my $type = ref($_[0]); Carp::cluck("No type in autoload") if ! $type; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/User.pm b/scripts/ZoneMinder/lib/ZoneMinder/User.pm new file mode 100644 index 000000000..47ba895a9 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/User.pm @@ -0,0 +1,80 @@ +# ========================================================================== +# +# ZoneMinder User Module +# Copyright (C) 2020 ZoneMinder LLC +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# ========================================================================== + +package ZoneMinder::Frame; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Object; + +use parent qw(ZoneMinder::Object); + +use vars qw/ $table $primary_key %fields /; +$table = 'Users'; +$primary_key = 'Id'; + +%fields = map { $_ => $_ } qw( +Id +Username +Password +Language +Enabled +Stream +Events +Control +Monitors +Groups +Devices +System +MaxBandwidth +MonitorIds +TokenMinExpiry +APIEnabled +); + +1; +__END__ + +=head1 NAME + +ZoneMinder::User - Perl Class for Users + +=head1 SYNOPSIS + +use ZoneMinder::User; + +=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/Zone.pm b/scripts/ZoneMinder/lib/ZoneMinder/Zone.pm new file mode 100644 index 000000000..97c6d32e8 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Zone.pm @@ -0,0 +1,113 @@ +# ========================================================================== +# +# ZoneMinder Zone Module +# Copyright (C) 2020 ZoneMinder LLC +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# ========================================================================== + +package ZoneMinder::Zone; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Object; + +#our @ISA = qw(Exporter ZoneMinder::Base); +use parent qw(ZoneMinder::Object); + +use vars qw/ $table $primary_key %fields $serial %defaults $debug/; +$table = 'Zones'; +$serial = $primary_key = 'Id'; +%fields = map { $_ => $_ } qw( + Id + Name + MonitorId + Type + Units + NumCoords + Coords + Area + AlarmRGB + CheckMethod + MinPixelThreshold + MaxPixelThreshold + MinAlarmPixels + MaxAlarmPixels + FilterX + FilterY + MinFilterPixels + MaxFilterPixels + MinBlobPixels + MaxBlobPixels + MinBlobs + MaxBlobs + OverloadFrames + ExtendAlarmFrames + ); + +%defaults = ( + Name => '', + Type => q`'Active'`, + Units => q`'Pixels'`, + NumCoords => 0, + Coords => '', + Area => 0, + AlarmRGB => 0, + CheckMethod => q`'Blobs'`, + MinPixelThreshold => undef, + MaxPixelThreshold => undef, + MinAlarmPixels => undef, + MaxAlarmPixels => undef, + FilterX => undef, + FilterY => undef, + MinFilterPixels => undef, + MaxFilterPixels => undef, + MinBlobPixels => undef, + MaxBlobPixels => undef, + MinBlobs => undef, + MaxBlobs => undef, + OverloadFrames => 0, + ExtendAlarmFrames => 0, +); + +1; +__END__ + +=head1 NAME + +ZoneMinder::Zone - Perl Class for Zones + +=head1 SYNOPSIS + +use ZoneMinder::Zone; + +=head1 AUTHOR + +Isaac Connor, Eisaac@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2017 ZoneMinder LLC + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index 9204ef3a1..180aee58f 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -205,7 +205,7 @@ MAIN: while( $loop ) { my $monitorSelectSth = $dbh->prepare_cached( $monitorSelectSql ) or Fatal( "Can't prepare '$monitorSelectSql': ".$dbh->errstr() ); - my $eventSelectSql = 'SELECT `Id`, (unix_timestamp() - unix_timestamp(`StartTime`)) AS Age + my $eventSelectSql = 'SELECT `Id`, (unix_timestamp() - unix_timestamp(`StartDateTime`)) AS Age FROM `Events` WHERE `MonitorId` = ?'.(@Storage_Areas ? ' AND `StorageId` IN ('.join(',',map { '?'} @Storage_Areas).')' : '' ). ' ORDER BY `Id`'; my $eventSelectSth = $dbh->prepare_cached( $eventSelectSql ) or Fatal( "Can't prepare '$eventSelectSql': ".$dbh->errstr() ); @@ -308,7 +308,7 @@ MAIN: while( $loop ) { } else { my $full_path = join('/', $Storage->Path(), $day_dir, $event_path); # Check storage id - if ( !$Event->Storage()->Id() ) { + if ( $Storage->Id() and !$Event->Storage()->Id() ) { Info("Correcting StorageId for event $$Event{Id} from $$Event{StorageId} $$Event{Path} to $$Storage{Id} $full_path"); $Event->save({ StorageId=>$Storage->Id() }); $Event->Path(undef); @@ -397,13 +397,13 @@ MAIN: while( $loop ) { my ( undef, $year, $month, $day ) = split('/', $day_dir); $year += 2000; my ( $hour, $minute, $second ) = split('/', $event_dir); - my $StartTime =sprintf('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', $year, $month, $day, $hour, $minute, $second); + my $StartDateTime =sprintf('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', $year, $month, $day, $hour, $minute, $second); my $Event = ZoneMinder::Event->find_one( MonitorId=>$monitor_dir, - StartTime=>$StartTime, + StartDateTime=>$StartDateTime, ); if ( $Event ) { - Debug("Found event matching starttime on monitor $monitor_dir at $StartTime: " . $Event->to_string()); + Debug("Found event matching StartDateTime on monitor $monitor_dir at $StartDateTime: " . $Event->to_string()); next; } aud_print("Deleting event directories with no event id information at $day_dir/$event_dir"); @@ -420,15 +420,15 @@ MAIN: while( $loop ) { Debug("Checking for Medium Scheme Events under $$Storage{Path}/$monitor_dir"); { my @event_dirs = glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*"); - Debug(qq`glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned ` . scalar @event_dirs . ' entries.' ); + Debug('glob("'.$monitor_dir.'/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned '.(scalar @event_dirs).' entries.'); foreach my $event_dir ( @event_dirs ) { if ( ! -d $event_dir ) { - Debug( "$event_dir is not a dir. Skipping" ); + Debug("$event_dir is not a dir. Skipping"); next; } my ( $date, $event_id ) = $event_dir =~ /^$monitor_dir\/(\d{4}\-\d{2}\-\d{2})\/(\d+)$/; - if ( ! $event_id ) { - Debug("Unable to parse date/event_id from $event_dir"); + if ( !$event_id ) { + Debug('Unable to parse date/event_id from '.$event_dir); next; } my $Event = $fs_events->{$event_id} = new ZoneMinder::Event(); @@ -438,8 +438,9 @@ MAIN: while( $loop ) { $Event->MonitorId( $monitor_dir ); $Event->StorageId( $Storage->Id() ); $Event->Path(); + $Event->age(); Debug("Have event $$Event{Id} at $$Event{Path}"); - $Event->StartTime( POSIX::strftime('%Y-%m-%d %H:%M:%S', gmtime(time_of_youngest_file($Event->Path())) ) ); + $Event->StartDateTime(POSIX::strftime('%Y-%m-%d %H:%M:%S', gmtime(time_of_youngest_file($Event->Path())))); } # end foreach event } @@ -466,13 +467,13 @@ MAIN: while( $loop ) { } # end foreach event chdir( $Storage->Path() ); } # if USE_DEEP_STORAGE - Debug( 'Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir" ); + Debug('Got '.int(keys(%$fs_events)).' filesystem events for monitor '.$monitor_dir); delete_empty_subdirs($$Storage{Path}.'/'.$monitor_dir); } # end foreach monitor if ( $cleaned ) { - Debug("First stage cleaning done. Restarting."); + Debug('First stage cleaning done. Restarting.'); redo MAIN; } @@ -484,7 +485,7 @@ MAIN: while( $loop ) { next; } my @event_ids = keys %$fs_events; - Debug('Have ' .scalar @event_ids . " events for monitor $monitor_id"); + Debug('Have ' .scalar @event_ids . ' events for monitor '.$monitor_id); foreach my $fs_event_id ( sort { $a <=> $b } keys %$fs_events ) { @@ -499,8 +500,8 @@ MAIN: while( $loop ) { } my $age = $Event->age(); - if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { - aud_print( "Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old" ); + if ( $age and ($age > $Config{ZM_AUDIT_MIN_AGE}) ) { + aud_print("Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old"); if ( confirm() ) { $Event->delete_files(); $cleaned = 1; @@ -586,12 +587,12 @@ EVENT: while ( my ( $db_event, $age ) = each( %$db_events ) ) { } else { Debug("$$Event{Id} Not found at $path"); } - } + } # end foreach Storage if ( $Event->Archived() ) { Warning("Event $$Event{Id} is Archived. Taking no further action on it."); next; } - if ( !$Event->StartTime() ) { + if ( !$Event->StartDateTime() ) { aud_print("Event $$Event{Id} has no start time."); if ( confirm() ) { $Event->delete(); @@ -599,7 +600,7 @@ EVENT: while ( my ( $db_event, $age ) = each( %$db_events ) ) { } next; } - if ( ! $Event->EndTime() ) { + if ( ! $Event->EndDateTime() ) { if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { aud_print("Event $$Event{Id} has no end time and is $age seconds old. Deleting it."); if ( confirm() ) { @@ -638,18 +639,13 @@ EVENT: while ( my ( $db_event, $age ) = each( %$db_events ) ) { Info("Updating storage area on event $$Event{Id} from $$Event{StorageId} to $$fs_events{$db_event}{StorageId}"); $Event->StorageId($$fs_events{$db_event}->StorageId()); } - if ( $$fs_events{$db_event}->StartTime() ne $Event->StartTime() ) { - Info("Updating StartTime on event $$Event{Id} from $$Event{StartTime} to $$fs_events{$db_event}{StartTime}"); - if ( $$Event{Scheme} eq 'Deep' ) { - $Event->StartTime($$fs_events{$db_event}->StartTime()); - } else { - $Event->StartTime($$fs_events{$db_event}->StartTime()); - } - $Event->save(); + if ( ! $Event->StartDateTime() ) { + Info("Updating StartDateTime on event $$Event{Id} from $$Event{StartDateTime} to $$fs_events{$db_event}{StartDateTime}"); + $Event->StartDateTime($$fs_events{$db_event}->StartDateTime()); } $Event->save(); - } + } # end if Event exists in db and not in filesystem } # end if ! in fs_events } # foreach db_event } # end foreach db_monitor @@ -687,12 +683,12 @@ if ( $level > 1 ) { # Remove empty events (with no frames) $cleaned = 0; Debug("Checking for Events with no Frames"); - my $selectEmptyEventsSql = 'SELECT `E`.`Id` AS `Id`, `E`.`StartTime`, `F`.`EventId` FROM `Events` AS E LEFT JOIN `Frames` AS F ON (`E`.`Id` = `F`.`EventId`) - WHERE isnull(`F`.`EventId`) AND now() - interval '.$Config{ZM_AUDIT_MIN_AGE}.' second > `E`.`StartTime`'; + my $selectEmptyEventsSql = 'SELECT `E`.`Id` AS `Id`, `E`.`StartDateTime`, `F`.`EventId` FROM `Events` AS E LEFT JOIN `Frames` AS F ON (`E`.`Id` = `F`.`EventId`) + WHERE isnull(`F`.`EventId`) AND now() - interval '.$Config{ZM_AUDIT_MIN_AGE}.' second > `E`.`StartDateTime`'; if ( my $selectEmptyEventsSth = $dbh->prepare_cached( $selectEmptyEventsSql ) ) { if ( $res = $selectEmptyEventsSth->execute() ) { while( my $event = $selectEmptyEventsSth->fetchrow_hashref() ) { - aud_print("Found empty event with no frame records '$event->{Id}' at $$event{StartTime}"); + aud_print("Found empty event with no frame records '$event->{Id}' at $$event{StartDateTime}"); if ( confirm() ) { if ( $res = $deleteEventSth->execute($event->{Id}) ) { $cleaned = 1; @@ -754,7 +750,7 @@ if ( $level > 1 ) { #"SELECT E.Id, ANY_VALUE(E.MonitorId), # #max(F.TimeStamp) as EndTime, -#unix_timestamp(max(F.TimeStamp)) - unix_timestamp(E.StartTime) as Length, +#unix_timestamp(max(F.TimeStamp)) - unix_timestamp(E.StartDateTime) as Length, #max(F.FrameId) as Frames, #count(if(F.Score>0,1,NULL)) as AlarmFrames, #sum(F.Score) as TotScore, @@ -764,11 +760,11 @@ if ( $level > 1 ) { #WHERE isnull(E.Frames) or isnull(E.EndTime) #GROUP BY E.Id HAVING EndTime < (now() - interval ".$Config{ZM_AUDIT_MIN_AGE}.' second)' #; - 'SELECT *, unix_timestamp(`StartTime`) AS `TimeStamp` FROM `Events` WHERE `EndTime` IS NULL AND `StartTime` < (now() - interval '.$Config{ZM_AUDIT_MIN_AGE}.' second)'.($monitor_id?' AND MonitorId=?':''); + 'SELECT *, unix_timestamp(`StartDateTime`) AS `TimeStamp` FROM `Events` WHERE `EndDateTime` IS NULL AND `StartDateTime` < (now() - interval '.$Config{ZM_AUDIT_MIN_AGE}.' second)'.($monitor_id?' AND MonitorId=?':''); my $selectFrameDataSql = ' SELECT - max(`TimeStamp`) AS `EndTime`, + max(`TimeStamp`) AS `EndDateTime`, unix_timestamp(max(`TimeStamp`)) AS `EndTimeStamp`, max(`FrameId`) AS `Frames`, count(if(`Score`>0,1,NULL)) AS `AlarmFrames`, @@ -783,7 +779,7 @@ FROM `Frames` WHERE `EventId`=?'; my $updateUnclosedEventsSql = "UPDATE low_priority `Events` SET `Name` = ?, - `EndTime` = ?, + `EndDateTime` = ?, `Length` = ?, `Frames` = ?, `AlarmFrames` = ?, @@ -798,7 +794,7 @@ FROM `Frames` WHERE `EventId`=?'; $res = $selectUnclosedEventsSth->execute($monitor_id?$monitor_id:()) or Fatal("Can't execute: ".$selectUnclosedEventsSth->errstr()); while( my $event = $selectUnclosedEventsSth->fetchrow_hashref() ) { - aud_print("Found open event '$event->{Id}' on Monitor $event->{MonitorId} at $$event{StartTime}"); + aud_print("Found open event '$event->{Id}' on Monitor $event->{MonitorId} at $$event{StartDateTime}"); if ( confirm('close', 'closing') ) { if ( ! ( $res = $selectFrameDataSth->execute($event->{Id}) ) ) { Error("Can't execute: $selectFrameDataSql:".$selectFrameDataSth->errstr()); @@ -812,7 +808,7 @@ FROM `Frames` WHERE `EventId`=?'; $event->{Id}, RECOVER_TAG ), - $frame->{EndTime}, + $frame->{EndDateTime}, $frame->{EndTimeStamp} - $event->{TimeStamp}, $frame->{Frames}, $frame->{AlarmFrames}, @@ -889,11 +885,11 @@ FROM `Frames` WHERE `EventId`=?'; $loop = $continuous; my $eventcounts_sql = ' - UPDATE `Monitors` SET - `TotalEvents`=(SELECT COUNT(`Id`) FROM `Events` WHERE `MonitorId`=`Monitors`.`Id`), - `TotalEventDiskSpace`=(SELECT SUM(`DiskSpace`) FROM `Events` WHERE `MonitorId`=`Monitors`.`Id` AND `DiskSpace` IS NOT NULL), - `ArchivedEvents`=(SELECT COUNT(`Id`) FROM `Events` WHERE `MonitorId`=`Monitors`.`Id` AND `Archived`=1), - `ArchivedEventDiskSpace`=(SELECT SUM(`DiskSpace`) FROM `Events` WHERE `MonitorId`=`Monitors`.`Id` AND `Archived`=1 AND `DiskSpace` IS NOT NULL) + UPDATE `Event_Summaries` SET + `TotalEvents`=(SELECT COUNT(`Id`) FROM `Events` WHERE `MonitorId`=`Event_Summaries`.`MonitorId`), + `TotalEventDiskSpace`=(SELECT SUM(`DiskSpace`) FROM `Events` WHERE `MonitorId`=`Event_Summaries`.`MonitorId` AND `DiskSpace` IS NOT NULL), + `ArchivedEvents`=(SELECT COUNT(`Id`) FROM `Events` WHERE `MonitorId`=`Event_Summaries`.`MonitorId` AND `Archived`=1), + `ArchivedEventDiskSpace`=(SELECT SUM(`DiskSpace`) FROM `Events` WHERE `MonitorId`=`Event_Summaries`.`MonitorId` AND `Archived`=1 AND `DiskSpace` IS NOT NULL) '; my $eventcounts_sth = $dbh->prepare_cached( $eventcounts_sql ); @@ -901,40 +897,40 @@ FROM `Frames` WHERE `EventId`=?'; $eventcounts_sth->finish(); my $eventcounts_hour_sql = ' - UPDATE `Monitors` INNER JOIN ( + UPDATE `Event_Summaries` INNER JOIN ( SELECT `MonitorId`, COUNT(*) AS `HourEvents`, SUM(COALESCE(`DiskSpace`,0)) AS `HourEventDiskSpace` FROM `Events_Hour` GROUP BY `MonitorId` - ) AS `E` ON `E`.`MonitorId`=`Monitors`.`Id` SET - `Monitors`.`HourEvents` = `E`.`HourEvents`, - `Monitors`.`HourEventDiskSpace` = `E`.`HourEventDiskSpace` + ) AS `E` ON `E`.`MonitorId`=`Event_Summaries`.`MonitorId` SET + `Event_Summaries`.`HourEvents` = `E`.`HourEvents`, + `Event_Summaries`.`HourEventDiskSpace` = `E`.`HourEventDiskSpace` '; my $eventcounts_day_sql = ' - UPDATE `Monitors` INNER JOIN ( + UPDATE `Event_Summaries` INNER JOIN ( SELECT `MonitorId`, COUNT(*) AS `DayEvents`, SUM(COALESCE(`DiskSpace`,0)) AS `DayEventDiskSpace` FROM `Events_Day` GROUP BY `MonitorId` - ) AS `E` ON `E`.`MonitorId`=`Monitors`.`Id` SET - `Monitors`.`DayEvents` = `E`.`DayEvents`, - `Monitors`.`DayEventDiskSpace` = `E`.`DayEventDiskSpace` + ) AS `E` ON `E`.`MonitorId`=`Event_Summaries`.`MonitorId` SET + `Event_Summaries`.`DayEvents` = `E`.`DayEvents`, + `Event_Summaries`.`DayEventDiskSpace` = `E`.`DayEventDiskSpace` '; my $eventcounts_week_sql = ' - UPDATE `Monitors` INNER JOIN ( + UPDATE `Event_Summaries` INNER JOIN ( SELECT `MonitorId`, COUNT(*) AS `WeekEvents`, SUM(COALESCE(`DiskSpace`,0)) AS `WeekEventDiskSpace` FROM `Events_Week` GROUP BY `MonitorId` - ) AS `E` ON `E`.`MonitorId`=`Monitors`.`Id` SET - `Monitors`.`WeekEvents` = `E`.`WeekEvents`, - `Monitors`.`WeekEventDiskSpace` = `E`.`WeekEventDiskSpace` + ) AS `E` ON `E`.`MonitorId`=`Event_Summaries`.`MonitorId` SET + `Event_Summaries`.`WeekEvents` = `E`.`WeekEvents`, + `Event_Summaries`.`WeekEventDiskSpace` = `E`.`WeekEventDiskSpace` '; my $eventcounts_month_sql = ' - UPDATE `Monitors` INNER JOIN ( + UPDATE `Event_Summaries` INNER JOIN ( SELECT `MonitorId`, COUNT(*) AS `MonthEvents`, SUM(COALESCE(`DiskSpace`,0)) AS `MonthEventDiskSpace` FROM `Events_Month` GROUP BY `MonitorId` - ) AS `E` ON `E`.`MonitorId`=`Monitors`.`Id` SET - `Monitors`.`MonthEvents` = `E`.`MonthEvents`, - `Monitors`.`MonthEventDiskSpace` = `E`.`MonthEventDiskSpace` + ) AS `E` ON `E`.`MonitorId`=`Event_Summaries`.`MonitorId` SET + `Event_Summaries`.`MonthEvents` = `E`.`MonthEvents`, + `Event_Summaries`.`MonthEventDiskSpace` = `E`.`MonthEventDiskSpace` '; my $eventcounts_hour_sth = $dbh->prepare_cached($eventcounts_hour_sql); my $eventcounts_day_sth = $dbh->prepare_cached($eventcounts_day_sql); @@ -1063,6 +1059,7 @@ sub delete_empty_directories { } } # end sub delete_empty_directories +# The idea is that the youngest file in the event directory gives us the event starttime sub time_of_youngest_file { my $dir = shift; @@ -1071,14 +1068,13 @@ sub time_of_youngest_file { return; } my $youngest = (stat($dir))[9]; - Debug("stat of $dir is $youngest"); + Debug("stat of dir $dir is $youngest"); foreach my $file ( readdir( DIR ) ) { next if $file =~ /^\./; $_ = (stat($dir))[9]; $youngest = $_ if $_ and ( $_ < $youngest ); - #Debug("stat of $dir is $_ < $youngest"); + Debug("Found younger file $file at $youngest"); } - Debug("stat of $dir is $youngest"); return $youngest; } # end sub time_of_youngest_file diff --git a/scripts/zmcamtool.pl.in b/scripts/zmcamtool.pl.in index 603c979bb..3d68b1408 100644 --- a/scripts/zmcamtool.pl.in +++ b/scripts/zmcamtool.pl.in @@ -351,8 +351,14 @@ sub exportsql { } } - if ($ARGV[0]) { - $command .= qq( --where="Name = '$ARGV[0]'"); + my $name = $ARGV[0]; + if ( $name ) { + if ( $name =~ /^([A-Za-z0-9 ,.&()\/\-]+)$/ ) { # Allow alphanumeric and " ,.&()/-" + $name = $1; + $command .= qq( --where="Name = '$name'"); + } else { + print "Invalid characters in Name\n"; + } } $command .= " zm Controls MonitorPresets"; diff --git a/scripts/zmcontrol.pl.in b/scripts/zmcontrol.pl.in index b68697ebc..a9f26ac80 100644 --- a/scripts/zmcontrol.pl.in +++ b/scripts/zmcontrol.pl.in @@ -27,10 +27,9 @@ use strict; use ZoneMinder; use Getopt::Long; use autouse 'Pod::Usage'=>qw(pod2usage); -use POSIX qw/strftime EPIPE/; +use POSIX qw/strftime EPIPE EINTR/; use Socket; use Data::Dumper; -use Module::Load::Conditional qw{can_load}; use constant MAX_CONNECT_DELAY => 15; use constant MAX_COMMAND_WAIT => 1800; @@ -49,9 +48,9 @@ my %options; GetOptions( 'id=i' =>\$id, 'command=s' =>\$options{command}, - 'xcoord=i' =>\$options{xcoord}, - 'ycoord=i' =>\$options{ycoord}, - 'speed=i' =>\$options{speed}, + 'xcoord=f' =>\$options{xcoord}, + 'ycoord=f' =>\$options{ycoord}, + 'speed=f' =>\$options{speed}, 'step=i' =>\$options{step}, 'panspeed=i' =>\$options{panspeed}, 'tiltspeed=i' =>\$options{tiltspeed}, @@ -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,14 +75,14 @@ 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; my $server_up; while ( $tries and ! ( $server_up = connect(CLIENT, $saddr) ) ) { Debug("Failed to connect to zmcontrol server at $sock_file"); - runCommand("zmdc.pl start zmcontrol.pl --id=$id"); + runCommand("zmdc.pl start zmcontrol.pl --id $id"); sleep 1; $tries -= 1; } @@ -101,54 +100,43 @@ 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}; + require ZoneMinder::Monitor; - if ( -x $protocol ) { - # Protocol is actually a script! - # Holdover from previous versions - my $command .= $protocol.' '.$arg_string; - Debug($command); + my $monitor = ZoneMinder::Monitor->find_one(Id=>$id); + Fatal("Unable to load control data for monitor $id") if !$monitor; - 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); + 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'); } 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()) ); - $0 = $0." --id=$id"; + $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); } @@ -159,24 +147,24 @@ if ( $options{command} ) { listen(SERVER, SOMAXCONN) or Fatal("Can't listen: $!"); my $rin = ''; - vec( $rin, fileno(SERVER), 1 ) = 1; + vec($rin, fileno(SERVER), 1) = 1; my $win = $rin; my $ein = $win; my $timeout = MAX_COMMAND_WAIT; - while( 1 ) { + while (!$zm_terminate) { my $nfound = select(my $rout = $rin, undef, undef, $timeout); if ( $nfound > 0 ) { - if ( vec( $rout, fileno(SERVER), 1 ) ) { + if ( vec($rout, fileno(SERVER), 1) ) { my $paddr = accept(CLIENT, SERVER); my $message = ; + close(CLIENT); next if !$message; my $params = jsonDecode($message); - Debug( Dumper( $params ) ); + Debug(Dumper($params)); my $command = $params->{command}; - close(CLIENT); if ( $command eq 'quit' ) { last; } elsif ( $command ) { @@ -188,23 +176,23 @@ if ( $options{command} ) { Fatal('Bogus descriptor'); } } elsif ( $nfound < 0 ) { - if ( $! == EPIPE ) { + if ( $! == EINTR ) { + # Likely just SIGHUP + Debug("Can't select: $!"); + } elsif ( $! == EPIPE ) { Error("Can't select: $!"); } else { Fatal("Can't select: $!"); } } else { - #print( "Select timed out\n" ); - last; + Debug('Select timed out'); } - } # end while forever + } # end while !$zm_terminate Info("Control server $id/$protocol exiting"); unlink($sock_file); $control->close(); - exit(0); } # end if !server up - exit(0); 1; diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index 4ebb9211b..793049479 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -92,7 +92,6 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my @daemons = ( 'zmc', - 'zma', 'zmfilter.pl', 'zmaudit.pl', 'zmtrigger.pl', @@ -102,11 +101,12 @@ my @daemons = ( 'zmstats.pl', 'zmtrack.pl', 'zmcontrol.pl', + 'zm_rtsp_server', 'zmtelemetry.pl' ); if ( $Config{ZM_OPT_USE_EVENTNOTIFICATION} ) { - push @daemons,'zmeventnotification.pl'; + push @daemons, 'zmeventnotification.pl'; } my $command = shift @ARGV; @@ -160,7 +160,7 @@ if ( !$server_up ) { use ZoneMinder::Server qw(CpuLoad); if ( ! defined $dbh->do(q{UPDATE Servers SET Status=?,TotalMem=?,FreeMem=?,TotalSwap=?,FreeSwap=? WHERE Id=?}, undef, 'NotRunning', &totalmem, &freemem, &totalswap, &freeswap, $Config{ZM_SERVER_ID} ) ) { - Error("Failed Updating status of Server record to Not RUnning for Id=$Config{ZM_SERVER_ID}" . $dbh->errstr()); + Error('Failed Updating status of Server record to Not Running for Id='.$Config{ZM_SERVER_ID}.': '.$dbh->errstr()); } } # Server is not up. Some commands can still be handled @@ -173,7 +173,7 @@ if ( !$server_up ) { print("stopped\n"); exit(); } elsif ( $command ne 'startup' ) { - print('Unable to connect to server using socket at ' . SOCK_FILE . "\n"); + print('Unable to connect to server using socket at '.SOCK_FILE."\n"); exit(-1); } @@ -189,10 +189,10 @@ if ( !$server_up ) { socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) or Fatal("Can't open socket: $!"); my $attempts = 0; - while( !connect(CLIENT, $saddr) ) { + while ( !connect(CLIENT, $saddr) ) { $attempts++; - Debug('Waiting for zmdc.pl server process at '.SOCK_FILE.", attempt $attempts"); - Fatal("Can't connect: $!") if $attempts > MAX_CONNECT_DELAY; + Debug('Waiting for zmdc.pl server process at '.SOCK_FILE.', attempt '.$attempts); + Fatal('Can\'t connect to zmdc.pl server process at '.SOCK_FILE.': '.$!) if $attempts > MAX_CONNECT_DELAY; usleep(200000); } # end while } elsif ( defined($cpid) ) { @@ -239,7 +239,7 @@ use Sys::MemInfo qw(totalmem freemem totalswap freeswap); use ZoneMinder::Server qw(CpuLoad); #use Data::Dumper; -use constant KILL_DELAY => 60; # seconds to wait between sending TERM and sending KILL +use constant KILL_DELAY => 10; # seconds to wait between sending TERM and sending KILL our %cmd_hash; our %pid_hash; @@ -255,13 +255,13 @@ sub run { close($PID); } else { # Log not initialized at this point so use die instead - die "Can't open pid file at ".ZM_PID."\n"; + die 'Can\'t open pid file at '.ZM_PID."\n"; } my $fd = 0; # This also closes dbh and CLIENT and SERVER - while( $fd < POSIX::sysconf(&POSIX::_SC_OPEN_MAX) ) { + while ( $fd < POSIX::sysconf(&POSIX::_SC_OPEN_MAX) ) { POSIX::close($fd++); } @@ -301,13 +301,14 @@ sub run { my $timeout = 1; my $secs_count = 0; - while( !$zm_terminate ) { + while ( !$zm_terminate ) { if ( $Config{ZM_SERVER_ID} ) { if ( ! ( $secs_count % 60 ) ) { while ( (!$zm_terminate) and !($dbh and $dbh->ping()) ) { Warning("Not connected to db ($dbh)".($dbh?' ping('.$dbh->ping().')':''). ($DBI::errstr?" errstr($DBI::errstr)":'').' Reconnecting'); $dbh = zmDbConnect(); + sleep 10 if !$dbh; } last if $zm_terminate; @@ -428,25 +429,36 @@ sub start { # It's not running, or at least it's not been started by us $process = { daemon=>$daemon, args=>\@args, command=>$command, keepalive=>!undef }; } elsif ( $process->{pid} && $pid_hash{$process->{pid}} ) { - dPrint(ZoneMinder::Logger::INFO, "'$process->{command}' already running at " + if ($process->{term_sent_at}) { + dPrint(ZoneMinder::Logger::INFO, "'$process->{command}' was told to term at " + .strftime('%y/%m/%d %H:%M:%S', localtime($process->{term_sent_at})) + .", pid = $process->{pid}\n" + ); + $process->{keepalive} = !undef; + $process->{delay} = 0; + delete $terminating_processes{$command}; + } else { + dPrint(ZoneMinder::Logger::INFO, "'$process->{command}' already running at " .strftime('%y/%m/%d %H:%M:%S', localtime($process->{started})) .", pid = $process->{pid}\n" - ); + ); + } return; } + # We have to block SIGCHLD during fork to prevent races while we setup our records for it my $sigset = POSIX::SigSet->new; my $blockset = POSIX::SigSet->new(SIGCHLD); sigprocmask(SIG_BLOCK, $blockset, $sigset) or Fatal("Can't block SIGCHLD: $!"); # Apparently the child closing the db connection can affect the parent. zmDbDisconnect(); - if ( my $cpid = fork() ) { + if ( my $child_pid = fork() ) { $dbh = zmDbConnect(1); # This logReinit is required. Not sure why. logReinit(); - $process->{pid} = $cpid; + $process->{pid} = $child_pid; $process->{started} = time(); delete $process->{pending}; @@ -455,9 +467,9 @@ sub start { .", pid = $process->{pid}\n" ); - $cmd_hash{$process->{command}} = $pid_hash{$cpid} = $process; + $cmd_hash{$process->{command}} = $pid_hash{$child_pid} = $process; sigprocmask(SIG_SETMASK, $sigset) or Fatal("Can't restore SIGCHLD: $!"); - } elsif ( defined($cpid) ) { + } elsif ( defined($child_pid) ) { # Child process # Force reconnection to the db. $dbh got copied, but isn't really valid anymore. @@ -489,7 +501,7 @@ sub start { zmDbDisconnect(); my $fd = 3; # leave stdin,stdout,stderr open. Closing them causes problems with libx264 - while( $fd < POSIX::sysconf(&POSIX::_SC_OPEN_MAX) ) { + while ( $fd < POSIX::sysconf(&POSIX::_SC_OPEN_MAX) ) { POSIX::close($fd++); } @@ -521,7 +533,7 @@ sub send_stop { ."\n" ); sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n"; - return(); + return (); } my $pid = $process->{pid}; @@ -584,7 +596,7 @@ sub check_for_processes_to_kill { sub stop { my ( $daemon, @args ) = @_; - my $command = join(' ', $daemon, @args ); + my $command = join(' ', $daemon, @args); my $process = $cmd_hash{$command}; if ( !$process ) { dPrint(ZoneMinder::Logger::WARNING, "Can't find process with command of '$command'"); @@ -610,6 +622,7 @@ sub restart { # Start will be handled by the reaper... # unless it was already pending in which case send_stop will return () so we should start it if ( !send_stop(0, $process) ) { + dPrint(ZoneMinder::Logger::WARNING, "!send_stop so starting '$command'\n"); start($daemon, @args); } return; diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index be9996fe2..3fb286039 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -21,28 +21,6 @@ # # ========================================================================== -=head1 NAME - -zmfilter.pl - ZoneMinder tool to filter events - -=head1 SYNOPSIS - -zmfilter.pl [-f ,--filter=] [--filter_id=] | -v, --version - -=head1 DESCRIPTION - -This script continuously monitors the recorded events for the given -monitor and applies any filters which would delete and/or upload -matching events. - -=head1 OPTIONS - - --f{filter name}, --filter={filter name} - The name of a specific filter to run ---filter_id={filter id} - The id of a specific filter to run --v, --version - Print ZoneMinder version - -=cut use strict; use bytes; @@ -98,6 +76,7 @@ use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|) ; logInit($filter_id?(id=>'zmfilter_'.$filter_id):()); + sub HupHandler { # This idea at this time is to just exit, freeing up the memory. # zmfilter.pl will be respawned by zmdc. @@ -159,19 +138,19 @@ $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; -my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL}; my $event_id = 0; -if ( ! EVENT_PATH ) { - Error("No event path defined. Config was $Config{ZM_DIR_EVENTS}"); +if (!EVENT_PATH) { + Error('No event path defined. Config was '.$Config{ZM_DIR_EVENTS}); die; } # In future, should not be neccessary wrt StorageAreas -chdir( EVENT_PATH ); +chdir(EVENT_PATH); # Should not be neccessary... but nice to get a local var. What if it fails? my $dbh = zmDbConnect(); +$dbh->do('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED'); if ( $filter_name ) { Info("Scanning for events using filter '$filter_name'"); @@ -193,26 +172,37 @@ if ( ! ( $filter_name or $filter_id ) ) { my @filters; my $last_action = 0; -while( !$zm_terminate ) { +while (!$zm_terminate) { + my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL}; my $now = time; - if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) { - Debug("Reloading filters"); + if (($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY}) { + Debug('Reloading filters'); $last_action = $now; @filters = getFilters({ Name=>$filter_name, Id=>$filter_id }); } - foreach my $filter ( @filters ) { + foreach my $filter (@filters) { last if $zm_terminate; - if ( $$filter{Concurrent} and ! ( $filter_id or $filter_name ) ) { + + my $elapsed = ($now - ($$filter{last_ran} ? $$filter{last_ran} : 0)); + if ($$filter{last_ran} and ($elapsed < $$filter{ExecuteInterval})) { + my $filter_delay = $$filter{ExecuteInterval} - ($now - $$filter{last_ran}); + $delay = $filter_delay if $filter_delay < $delay; + Debug("Setting delay to $delay because ExecuteInterval=$$filter{ExecuteInterval} and $elapsed have elapsed"); + next; + } + + if ($$filter{Concurrent} and !($filter_id or $filter_name)) { my ( $proc ) = $0 =~ /(\S+)/; my ( $id ) = $$filter{Id} =~ /(\d+)/; Debug("Running concurrent filter process $proc --filter_id $$filter{Id} => $id for $$filter{Name}"); - system(qq`$proc --filter "$$filter{Name}" &`); + system(qq`$proc --filter_id $id &`); } else { checkFilter($filter); + $$filter{last_ran} = $now; } - } + } # end foreach filter last if (!$daemon and ($filter_name or $filter_id)) or $zm_terminate; @@ -236,6 +226,7 @@ sub getFilters { $sql .= ' `Background` = 1 AND'; } $sql .= '( `AutoArchive` = 1 + or `AutoUnarchive` = 1 or `AutoVideo` = 1 or `AutoUpload` = 1 or `AutoEmail` = 1 @@ -275,6 +266,7 @@ FILTER: while( my $db_filter = $sth->fetchrow_hashref() ) { sub checkFilter { my $filter = shift; + my $in_transaction = ZoneMinder::Database::start_transaction($dbh) if $$filter{LockRows}; my @Events = $filter->Execute(); Debug( join(' ', @@ -282,6 +274,7 @@ sub checkFilter { join(', ', ($filter->{AutoDelete}?'delete':()), ($filter->{AutoArchive}?'archive':()), + ($filter->{AutoUnarchive}?'unarchive':()), ($filter->{AutoVideo}?'video':()), ($filter->{AutoUpload}?'upload':()), ($filter->{AutoEmail}?'email':()), @@ -299,7 +292,7 @@ sub checkFilter { last if $zm_terminate; my $Event = new ZoneMinder::Event($$event{Id}, $event); - Debug("Checking event $Event->{Id}"); + Debug('Checking event '.$Event->{Id}); my $delete_ok = !undef; $dbh->ping(); if ( $filter->{AutoArchive} ) { @@ -311,6 +304,15 @@ sub checkFilter { my $res = $sth->execute($Event->{Id}) or Error("Unable to execute '$sql': ".$dbh->errstr()); } + if ( $filter->{AutoUnarchive} ) { + Info("Unarchiving event $Event->{Id}"); + # Do it individually to avoid locking up the table for new events + my $sql = 'UPDATE `Events` SET `Archived` = 0 WHERE `Id` = ?'; + my $sth = $dbh->prepare_cached($sql) + or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute($Event->{Id}) + or Error("Unable to execute '$sql': ".$dbh->errstr()); + } if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) { if ( !$Event->{Videoed} ) { $delete_ok = undef if !generateVideo($filter, $Event); @@ -346,6 +348,7 @@ sub checkFilter { if ( $filter->{AutoMove} ) { my $NewStorage = new ZoneMinder::Storage($filter->{AutoMoveTo}); + Info("Moving event $Event->{Id} to datastore $filter->{AutoMoveTo}"); $_ = $Event->MoveTo($NewStorage); Error($_) if $_; } @@ -354,6 +357,7 @@ sub checkFilter { # So we still need to update the Event object with the new SecondaryStorageId my $NewStorage = ZoneMinder::Storage->find_one(Id=>$filter->{AutoCopyTo}); if ( $NewStorage ) { + Info("Copying event $Event->{Id} to datastore $filter->{AutoCopyTo}"); $_ = $Event->CopyTo($NewStorage); if ( $_ ) { $ZoneMinder::Database::dbh->commit(); @@ -368,9 +372,6 @@ sub checkFilter { } # end if AutoCopy if ( $filter->{UpdateDiskSpace} ) { - $ZoneMinder::Database::dbh->begin_work(); - $Event->lock_and_load(); - my $old_diskspace = $$Event{DiskSpace}; my $new_diskspace = $Event->DiskSpace(undef); @@ -381,9 +382,9 @@ sub checkFilter { ) { $Event->save(); } - $ZoneMinder::Database::dbh->commit(); } # end if UpdateDiskSpace } # end foreach event + ZoneMinder::Database::end_transaction($dbh, $in_transaction) if $$filter{LockRows}; } # end sub checkFilter sub generateVideo { @@ -391,8 +392,9 @@ sub generateVideo { my $Event = shift; my $phone = shift; - my $rate = $Event->{DefaultRate}/100; - my $scale = $Event->{DefaultScale}/100; + my $Monitor = $Event->Monitor(); + my $rate = $$Monitor{DefaultRate}/100; + my $scale = $$Monitor{DefaultScale}/100; my $format; my @ffmpeg_formats = split(/\s+/, $Config{ZM_FFMPEG_FORMATS}); @@ -462,15 +464,18 @@ sub generateImage { my $event_path = $Event->Path(); my $capture_image_path = sprintf('%s/%0'.$Config{ZM_EVENT_IMAGE_DIGITS}.'d-capture.jpg', $event_path, $frame->{FrameId}); my $analyse_image_path = sprintf('%s/%0'.$Config{ZM_EVENT_IMAGE_DIGITS}.'d-analyse.jpg', $event_path, $frame->{FrameId}) if $analyse; - my $video_path = sprintf('%s/%d-video.mp4', $event_path, $Event->{Id}); + my $video_path = sprintf('%s/%s', $event_path, $Event->{DefaultVideo}); my $image_path = ''; # check if the image file exists. If the file doesn't exist and we use H264 try to extract it from .mp4 video if ( $analyse && -r $analyse_image_path ) { + Debug("Using analysis and jpeg exists $analyse_image_path"); $image_path = $analyse_image_path; } elsif ( -r $capture_image_path ) { + Debug("Using captures and jpeg exists $capture_image_path"); $image_path = $capture_image_path; } elsif ( -r $video_path ) { + Debug("mp4 exists $video_path"); my $command ="ffmpeg -nostdin -ss $$frame{Delta} -i '$video_path' -frames:v 1 '$capture_image_path'"; #$command = "ffmpeg -y -v 0 -i $video_path -vf 'select=gte(n\\,$$frame{FrameId}),setpts=PTS-STARTPTS' -vframes 1 -f image2 $capture_image_path"; my $output = qx($command); @@ -484,6 +489,8 @@ sub generateImage { } else { $image_path = $capture_image_path; } + } else { + Debug("No files found at $analyse_image_path, $capture_image_path or $video_path"); } return $image_path; } @@ -507,7 +514,7 @@ sub uploadArchFile { return(0); } - my $archFile = $Event->{MonitorName}.'-'.$Event->{Id}; + my $archFile = $Event->Monitor()->Name().'-'.$Event->{Id}; my $archImagePath = $Event->Path() .'/' .( @@ -528,6 +535,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 ) { @@ -641,12 +652,14 @@ sub substituteTags { # First we'd better check what we need to get # We have a filter and an event, do we need any more # monitor information? - my $need_monitor = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; + my $need_monitor = $text =~ /%(?:MN|MET|MEH|MED|MEW|MEN|MEA)%/; + my $need_summary = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; my $Monitor = $Event->Monitor() if $need_monitor; + 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; @@ -667,19 +680,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%/$Event->{MonitorName}/g; - $text =~ s/%MET%/$Monitor->{TotalEvents}/g; - $text =~ s/%MEH%/$Monitor->{HourEvents}/g; - $text =~ s/%MED%/$Monitor->{DayEvents}/g; - $text =~ s/%MEW%/$Monitor->{WeekEvents}/g; - $text =~ s/%MEM%/$Monitor->{MonthEvents}/g; - $text =~ s/%MEA%/$Monitor->{ArchivedEvents}/g; + $text =~ s/%MN%/$Monitor->{Name}/g; + $text =~ s/%MET%/$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; @@ -690,7 +703,8 @@ sub substituteTags { $text =~ s/%EN%/$Event->{Name}/g; $text =~ s/%EC%/$Event->{Cause}/g; $text =~ s/%ED%/$Event->{Notes}/g; - $text =~ s/%ET%/$Event->{StartTime}/g; + $text =~ s/%ET%/$Event->{StartDateTime}/g; + $text =~ s/%EVF%/$$Event{DefaultVideo}/g; # Event video filename $text =~ s/%EL%/$Event->{Length}/g; $text =~ s/%EF%/$Event->{Frames}/g; $text =~ s/%EFA%/$Event->{AlarmFrames}/g; @@ -699,15 +713,22 @@ sub substituteTags { $text =~ s/%ESM%/$Event->{MaxScore}/g; if ( $first_alarm_frame ) { - $text =~ s/%EPI1%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$first_alarm_frame->{FrameId}/g; - $text =~ s/%EPIM%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$max_alarm_frame->{FrameId}/g; + $text =~ s/%EPF1%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$first_alarm_frame->{FrameId}/g; + $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); if ( -e $path ) { push @$attachments_ref, { type=>'image/jpeg', path=>$path }; } else { - Warning("Path to first image does not exist at $path"); + Warning("Path to first image does not exist at $path for image $first_alarm_frame"); } } @@ -749,12 +770,20 @@ sub substituteTags { } if ( $text =~ s/%EIMOD%//g ) { - $text =~ s/%EIMOD%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=objdetect/g; my $path = $Event->Path().'/objdetect.jpg'; if ( -e $path ) { push @$attachments_ref, { type=>'image/jpeg', path=>$path }; } else { - Warning('No image for EIMOD at ' . $path); + Warning('No image for MOD at '.$path); + } + } + + 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); } } @@ -796,51 +825,55 @@ sub sendEmail { Error('No from email address defined, not sending email'); return 0; } - if ( ! $Config{ZM_EMAIL_ADDRESS} ) { + if ( ! $$filter{EmailTo} ) { Error('No email address defined, not sending email'); return 0; } - Info('Creating notification email'); + Debug('Creating notification email'); - my $subject = substituteTags($Config{ZM_EMAIL_SUBJECT}, $filter, $Event); + my $subject = substituteTags($$filter{EmailSubject}, $filter, $Event); return 0 if !$subject; my @attachments; - my $body = substituteTags($Config{ZM_EMAIL_BODY}, $filter, $Event, \@attachments); + 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} ) { ### Create the multipart container my $mail = MIME::Lite->new ( From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_EMAIL_ADDRESS}, + To => $$filter{EmailTo}, Subject => $subject, Type => 'multipart/mixed' ); ### Add the text message part $mail->attach ( - Type => 'TEXT', + Type => (($body=~/ $body ); ### Add the attachments + my $total_size = 0; foreach my $attachment ( @attachments ) { - Info( "Attaching '$attachment->{path}'" ); + my $size = -s $attachment->{path}; + $total_size += $size; + Info("Attaching '$attachment->{path}' which is $size bytes"); $mail->attach( Path => $attachment->{path}, Type => $attachment->{type}, Disposition => 'attachment' ); } + if ( $total_size > 10*1024*1024 ) { + Warning('Emails larger than 10Mb will often not be delivered! This one is '.int($total_size/(1024*1024)).'Mb'); + } ### Send the Message 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 ) { @@ -849,7 +882,7 @@ sub sendEmail { $mail->send(); } else { ### Send using SSMTP - $mail->send('sendmail', $ssmtp_location, $Config{ZM_EMAIL_ADDRESS}); + $mail->send('sendmail', $ssmtp_location, $$filter{EmailTo}); } } else { MIME::Lite->send('smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60); @@ -857,20 +890,27 @@ sub sendEmail { } } else { my $mail = MIME::Entity->build( - From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_EMAIL_ADDRESS}, - Subject => $subject, - Type => (($body=~//)?'text/html':'text/plain'), - Data => $body - ); + From => $Config{ZM_FROM_EMAIL}, + To => $$filter{EmailTo}, + Subject => $subject, + Type => (($body=~/ $body + ); + my $total_size = 0; foreach my $attachment ( @attachments ) { - Info("Attaching '$attachment->{path}'"); + my $size = -s $attachment->{path}; + $total_size += $size; + Debug("Attaching '$attachment->{path}' which is $size bytes"); + $mail->attach( - Path => $attachment->{path}, - Type => $attachment->{type}, - Encoding => 'base64' - ); + Path => $attachment->{path}, + Type => $attachment->{type}, + Encoding => 'base64' + ); + } # end foreach attachment + if ( $total_size > 10*1024*1024 ) { + Warning('Emails larger than 10Mb will often not be delivered! This one is '.int($total_size/(1024*1024)).'Mb'); } $mail->smtpsend(Host => $Config{ZM_EMAIL_HOST}, MailFrom => $Config{ZM_FROM_EMAIL}); } @@ -879,7 +919,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) @@ -962,7 +1002,7 @@ sub sendMessage { From => $Config{ZM_FROM_EMAIL}, To => $Config{ZM_MESSAGE_ADDRESS}, Subject => $subject, - Type => (($body=~//)?'text/html':'text/plain'), + Type => (($body=~/ $body ); @@ -999,10 +1039,7 @@ sub executeCommand { my $filter = shift; my $Event = shift; - my $event_path = $Event->Path(); - - my $command = $filter->{AutoExecuteCmd}; - $command .= " $event_path"; + my $command = $filter->{AutoExecuteCmd}.' '.$Event->Path(); $command = substituteTags($command, $filter, $Event); Info("Executing '$command'"); @@ -1012,15 +1049,37 @@ sub executeCommand { chomp($output); Debug("Output: $output"); } - if ( $status ) { + if ($status) { Error("Command '$command' exited with status: $status"); return 0; } else { - my $sql = 'UPDATE `Events` SET `Executed` = 1 WHERE `Id` = ?'; - my $sth = $dbh->prepare_cached($sql) - or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); - my $res = $sth->execute( $Event->{Id} ) - or Fatal("Unable to execute '$sql': ".$dbh->errstr()); + ZoneMinder::Database::zmSQLExecute('UPDATE `Events` SET `Executed` = 1 WHERE `Id` = ?', $Event->{Id}); } - return( 1 ); + return 1; } + +1; +__END__ + +=head1 NAME + +zmfilter.pl - ZoneMinder tool to select events and perform actions on them + +=head1 SYNOPSIS + +zmfilter.pl [-f ,--filter=] [--filter_id=] [--daemon] | -v, --version + +=head1 DESCRIPTION + +This script performs a specified database query to select recorded events and performs specified actions on them +such as email reporting, deleting, moving, etc. If the --daemon option is given it will remain resident, repeating +the query and applying actions. This is normally managed by zmdc.pl however it can be used manually as well. + +=head1 OPTIONS + + -f{filter name}, --filter={filter name} - The name of a specific filter to run + --filter_id={filter id} - The id of a specific filter to run + --daemon - Causes zmfilter.pl to stay running endlessly repeating the filter(s). + -v, --version - Print ZoneMinder version + +=cut diff --git a/scripts/zmonvif-probe.pl.in b/scripts/zmonvif-probe.pl.in old mode 100755 new mode 100644 index 6b4c5c1a6..b9d3cd0bf --- a/scripts/zmonvif-probe.pl.in +++ b/scripts/zmonvif-probe.pl.in @@ -41,7 +41,7 @@ my $OPTIONS = 'v'; sub HELP_MESSAGE { my ($fh, $pkg, $ver, $opts) = @_; - print $fh "Usage: " . __FILE__ . " [-v] probe \n"; + print $fh "Usage: " . __FILE__ . " [-v] probe \n"; print $fh " " . __FILE__ . " [-v] \n"; print $fh <new( { + verbose => $ZoneMinder::ONVIF::verbose, url_svc_device => $url_svc_device, soap_version => $soap_version } ); @@ -101,17 +103,43 @@ if ( $action eq 'probe' ) { $client->create_services(); if ( $action eq 'profiles' ) { - ZoneMinder::ONVIF::profiles($client); - } elsif( $action eq 'move' ) { + my @profiles = ZoneMinder::ONVIF::profiles($client); + foreach my $profile ( @profiles ) { + my ( $token, $name, $encoding, $width, $height, $frame_rate_limit, $stream_type, $uri ) = @{$profile}; + print join(', ', $token, + $name, + $encoding, + $width, + $height, + $frame_rate_limit, + $stream_type, + $uri, + ) . "\n"; + + } # end foreach profile + } elsif ( $action eq 'move' ) { my $dir = shift; ZoneMinder::ONVIF::move($client, $dir); } elsif ( $action eq 'metadata' ) { ZoneMinder::ONVIF::metadata($client); } else { - print("Error: Unknown command \"$action\""); - exit(1); + my $media = $client->get_endpoint('media'); + if ( ! $media ) { + print "No media endpoint for client.\n"; + return; + } + + my $result = $media->$action( { } ,, ); + if ( $result ) { + use XML::LibXML; + my $dom = XML::LibXML->load_xml(string=>$result); + print "Received message:\n" . $dom->toString(1) . "\n"; + } else { + print("Error: Unknown command \"$action\""); + exit(1); + } } -} +} # end if probe or other 1; __END__ @@ -130,6 +158,8 @@ zmonvif-probe.pl - ZoneMinder ONVIF probing tool profiles - print the device's supported stream configurations metadata - print some of the device's configuration settings move - move the device (only ptz cameras) + other - Any command supported by the ONVIF Media element. + Common parameters: -v - increase verbosity Device access parameters (for all commands but 'probe'): diff --git a/scripts/zmonvif-trigger.pl.in b/scripts/zmonvif-trigger.pl.in new file mode 100644 index 000000000..942ee0af0 --- /dev/null +++ b/scripts/zmonvif-trigger.pl.in @@ -0,0 +1,682 @@ +#!@PERL_EXECUTABLE@ -w +# +# ========================================================================== +# +# ZoneMinder ONVIF Event Watcher Script +# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ========================================================================== +# +# This module contains the implementation of the ONVIF event watcher +# + +## SETUP: +## chmod g+rw /dev/shm/zm* +## chgrp users /dev/shm/zm* +## systemctl stop iptables + +## DEBUG: +## hexdump -x -s 0 -n 600 /dev/shm/zm.mmap.1 +## rm -rf /srv/www/html/zm/events/[...] +## + +use strict; +use bytes; + +use Carp; +use Data::Dump qw( dump ); + +use DBI; +use Getopt::Long qw(:config no_auto_abbrev no_ignore_case bundling); +use IO::Select; +use POSIX qw( EINTR ); +use Time::HiRes qw( usleep ); + +use SOAP::Lite; # +trace; +use SOAP::Transport::HTTP; + +use ZoneMinder; + +require ONVIF::Client; + +require WSNotification::Interfaces::WSBaseNotificationSender::NotificationProducerPort; +require WSNotification::Interfaces::WSBaseNotificationSender::SubscriptionManagerPort; +require WSNotification::Types::TopicExpressionType; + +# ======================================================================== +# Constants + +# in seconds +use constant SUBSCRIPTION_RENEW_INTERVAL => 60; #3600; +use constant SUBSCRIPTION_RENEW_EARLY => 5; # 60; +use constant MONITOR_RELOAD_INTERVAL => 3600; + +# ======================================================================== +# Globals + +my $verbose = 0; +my $script; +my $daemon_pid; + +my $monitor_reload_time = 0; + +# this does not work on all architectures +my @EXTRA_SOCK_OPTS = ( + 'ReuseAddr' => '1', + # 'ReusePort' => '1', + # 'Blocking' => '0', +); + + +# ========================================================================= +# signal handling + +sub handler { # 1st argument is signal name + my ($sig) = @_; + Error("Caught a SIG$sig -- shutting down"); + confess(); + kill($daemon_pid) if defined $daemon_pid; + exit(0); +} + +$SIG{INT} = \&handler; +$SIG{HUP} = \&handler; +$SIG{QUIT} = \&handler; +$SIG{TERM} = \&handler; +$SIG{__DIE__} = \&handler; + +# ========================================================================= +# Debug + +#use Data::Hexdumper qw(hexdump); +#use Devel::Peek; + +sub dump_mapped { + my ($monitor) = @_; + + if ( !zmMemVerify($monitor) ) { + print "Error: Mapped memory not accessible\n"; + } + my $mmap = $monitor->{MMap}; + print 'Size: '.$ZoneMinder::Memory::mem_size ."\n"; + printf("Mapped at %x\n", $monitor->{MMapAddr}); + # Dump($mmap); + if ( $mmap && $$mmap ) { + #print hexdump( + #data => $$mmap, # what to dump + #output_format => ' %4a : %S %S %S %S %S %S %S %S : %d', + ## start_position => 336, # start at this offset ... + #end_position => 400 # ... and end at this offset + #); + } +} + +#push @EXPORT, qw(dump_mapped); + +# ========================================================================= +# internal methods + +sub xs_duration { + use integer; + my ($seconds) = @_; + my $s = $seconds % 60; + $seconds /= 60; + my $m = $seconds % 60; + $seconds /= 60; + my $h = $seconds % 24; + $seconds /= 24; + my $d = $seconds; + my $str; + if($d > 0) { + $str = "P". $d; + } + else { + $str = "P"; + } + $str = $str . "T"; + if($h > 0) { + $str = $str . $h . "H"; + } + if($m > 0) { + $str = $str . $m . "M"; + } + if($s > 0) { + $str = $str . $s . "S"; + } + return $str; +} + +# ========================================================================= +### ZoneMinder integration + +## make this a singleton ? +{ + package _ZoneMinder; + + use strict; + use bytes; + + use base qw(Class::Std::Fast); + + use DBI; + use Encode qw(decode encode); + use Time::HiRes qw( usleep ); + use ZoneMinder; + + # my %monitors = (); + my $dbh; + my %monitors_of; # :ATTR(:name :default<{}>); + + sub init { + $dbh = zmDbConnect(); + } + + sub monitors { + my ($self) = @_; + return $monitors_of{ident $self}?%{ $monitors_of{ident $self} }:undef; + } + + sub set_monitors { + my ($self, %monitors_par) = @_; + $monitors_of{ident $self} = \%monitors_par; + } + + ## TODO: remember to refresh this in all threads (daemon is forked) + sub loadMonitors { + my ($self) = @_; + Info("Loading monitors"); + $monitor_reload_time = time(); + + my %new_monitors = (); + + # my $sql = "select * from Monitors where find_in_set( Function, 'Modect,Mocord,Nodect,ExtDect' )>0 and ConfigType='ONVIF'"; + my $sql = "SELECT * FROM Monitors WHERE ONVIF_URL != ''"; + my $sth = $dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute() or Fatal("Can't execute: ".$sth->errstr()); + while ( my $monitor = $sth->fetchrow_hashref() ) { + if ( !defined $script ) { + if ( !zmMemVerify($monitor) ) { # Check shared memory ok + zmMemInvalidate($monitor); + next if !zmMemVerify($monitor); + } + } + + Info('Monitor URL: '.$monitor->{ONVIF_URL}); + + ## set up ONVIF client for monitor + next if ! $monitor->{ONVIF_URL}; + + my $soap_version; + if ( $monitor->{ONVIF_Options} =~ /SOAP1([12])/ ) { + $soap_version = "1.$1"; + } else { + $soap_version = '1.1'; + } + my $client = ONVIF::Client->new( { + url_svc_device => $monitor->{ONVIF_URL}, + soap_version => $soap_version } ); + + if ( $monitor->{ONVIF_Username} ) { + $client->set_credentials($monitor->{ONVIF_Username}, $monitor->{ONVIF_Password}, 0); + } + + $client->create_services(); + $monitor->{onvif_client} = $client; + + $new_monitors{$monitor->{Id}} = $monitor; + } # end foreach monitor + $self->set_monitors(%new_monitors); + } # end foreach db monitor + + sub freeMonitors { + my ($self) = @_; + my %monitors = $self->monitors(); + foreach my $monitor ( values %monitors ) { + # Free up any used memory handle + zmMemInvalidate($monitor); + } + } + + sub eventOn { + my ($self, $monitorId, $score, $cause, $text, $showtext) = @_; + # Info( "Trigger '$trigger'\n" ); + Info("On: $monitorId, $score, $cause, $text, $showtext"); + my %monitors = $self->monitors(); + my $monitor = $monitors{$monitorId}; + if ( defined $script ) { + # eval { + system($script, 'On', $monitor->{Name}, $monitor->{Path}, $cause); + # this goes to "stopped" in ffmpeg when executed from shell - why? + # } + } else { + # encode() ensures that no utf-8 is written to mmap'ed memory. + zmTriggerEventOn($monitor, $score, encode('utf-8', $cause), encode('utf-8', $text)); + zmTriggerShowtext($monitor, encode('utf-8', $showtext)) if defined($showtext); + # main::dump_mapped($monitor); + } + } + + sub eventOff { + my ($self, $monitorId, $score, $cause, $text, $showtext) = @_; + Info("Off: $monitorId, $score, $cause, $text, $showtext"); + my %monitors = $self->monitors(); + my $monitor = $monitors{$monitorId}; + if ( defined $script ) { + # eval { + system($script, 'Off', $monitor->{Name}, $monitor->{Path}, $cause); + # } + } else { + my $last_event = zmGetLastEvent($monitor); + zmTriggerEventOff($monitor); + # encode() ensures that no utf-8 is written to mmap'ed memory. + zmTriggerShowtext($monitor, encode('utf-8', $showtext) ) if defined($showtext); + # Info( "Trigger '$trigger'\n" ); + # Wait til it's finished + while ( zmInAlarm($monitor) && ($last_event == zmGetLastEvent($monitor)) ) { + # Tenth of a second + usleep(100000); + } + zmTriggerEventCancel($monitor); + # main::dump_mapped($monitor); + } + } +} # end package _ZoneMinder +# ========================================================================= +### (experimental) send email + +sub send_picture_email { + # 'ffmpeg -i "rtsp://admin:admin123@192.168.0.70:554/Streaming/Channels/1?transportmode=mcast&profile=Profile_1" -y -frames 1 -vf scale=1024:-1 /tmp/pic2.jpg' +} + +# ========================================================================= +### Consumer for Notify messages + +$SOAP::Constants::DO_NOT_CHECK_MUSTUNDERSTAND = 1; + +## make this a singleton ? +{ + package _Consumer; + use strict; + use bytes; + use base qw(Class::Std::Fast SOAP::Server::Parameters); + + use ZoneMinder; + + my $zm; + + sub BUILD { + my ($self, $ident, $arg_ref) = @_; + # $zm_of{$ident} = check_name( $arg_ref->{zm} ); + $zm = $arg_ref->{zm}; + } + + # + # called on http://docs.oasis-open.org/wsn/bw-2/NotificationConsumer/Notify + # + sub Notify { + my ($self, $unknown, $som) = @_; + Debug('### Notify'); + my $req = $som->context->request; + # Data::Dump::dump($req); + my $id = 0; + if ( $req->uri->path =~ m|/ref_(.*)/| ) { + $id = $1; + } else { + Warning('Unknown URL '.$req->uri->path.' called by event'); + return (); + } + # Data::Dump::dump($som); + my $action = $som->valueof('/Envelope/Header/Action'); + Debug(' Action = '.$action); + my $msg = $som->match('/Envelope/Body/Notify/NotificationMessage'); + my $topic = $msg->valueof('Topic'); + my $msg2 = $msg->match('Message/Message'); + # Data::Dump::dump($msg2->current()); + my $time = $msg2->dataof()->attr->{'UtcTime'}; + + my (%source, %data); + foreach my $item ($msg2->dataof('Source/SimpleItem')) { + $source{$item->attr->{Name}} = $item->attr->{Value}; + # print $item->attr->{Name} ."=>". $item->attr->{Value} ."\n"; + } + foreach my $item ($msg2->dataof('Data/SimpleItem')) { + $data{$item->attr->{Name}} = $item->attr->{Value}; + } + Debug("Ref=$id, Topic=$topic, $time, Rule=$source{Rule}, isMotion=$data{IsMotion}"); + if ( lc($data{IsMotion}) eq 'true' ) { + $zm->eventOn($id, 100, $source{Rule}, $time); + } elsif ( lc($data{IsMotion}) eq 'false' ) { + $zm->eventOff($id, 100, $source{Rule}, $time); + } + return (); + } +} # end Consumer + +# ========================================================================= + +sub daemon_main { + my ($daemon) = @_; + + # $daemon->handle(); + + # improve responsiveness with multiple clients (cameras) + my $d = $daemon->{_daemon}; + my $select = IO::Select->new(); + $select->add($d); + while ($select->count() ) { + my @ready = $select->can_read(); # blocks + foreach my $connection (@ready) { + if ( $connection == $d ) { + # on the daemon accept and add the connection + my $client = $connection->accept(); + $select->add($client); + } else { + # it's a client connection + my $request = $connection->get_request(); + if ( $request ) { + # process the request (taken from SOAP::Transport::HTTP::Daemon->handle() ) + $daemon->request($request); + $daemon->SOAP::Transport::HTTP::Server::handle(); + eval { + local $SIG{PIPE} = sub { print("SIGPIPE\n") }; # die? + $connection->send_response( $daemon->response ); + }; + if ( $@ && $@ !~ /^SIGPIPE/ ) { + print($@); # die? + } + } else { + # connection was closed by the client + $select->remove($connection); + $connection->close(); # is this necessary? + } + } # end if new connection or existing + } # end foreach connection + } # end while select->count +} # end sub daemon_main + +sub start_daemon { + my ($localip, $localport, $zm) = @_; + + ### daemon + my $daemon = SOAP::Transport::HTTP::Daemon->new( + LocalAddr => $localip, + LocalPort => $localport, + # 'deserializer' => $deserializer, + @EXTRA_SOCK_OPTS + ); + + ## handling + + # we only handle one method + $daemon->on_dispatch( sub { + return ( 'http://docs.oasis-open.org/wsn/bw-2/NotificationConsumer', 'Notify' ); + }); + + $daemon_pid = fork(); + die "fork() failed: $!" unless defined $daemon_pid; + if ( $daemon_pid ) { + + # this is a new process --> use new name and log file + $0 = $0.' [http-daemon]'; + logInit(id => 'zmonvif-trigger-httpd'); + logSetSignal(); + + # $zm is copied and the mmap'ed regions still exist + my $consumer = _Consumer->new({zm => $zm}); + $daemon->dispatch_with({ + # "http://docs.oasis-open.org/wsn/bw-2" => $consumer, + 'http://docs.oasis-open.org/wsn/bw-2/NotificationConsumer' => $consumer, + }); + daemon_main($daemon); + } else { + return $daemon; + } +} # end sub start_daemon + +require WSNotification::Elements::Subscribe; +require WSNotification::Types::EndpointReferenceType; +#require WSNotification::Types::ReferenceParametersType; +#require WSNotification::Elements::Metadata; +require WSNotification::Types::FilterType; +require WSNotification::Elements::TopicExpression; +require WSNotification::Elements::MessageContent; +require WSNotification::Types::AbsoluteOrRelativeTimeType; +require WSNotification::Types::AttributedURIType; + +sub subscribe { + my ($client, $localaddr, $topic_str, $duration, $ref_id) = @_; + + # for debugging: + # $client->get_endpoint('events')->no_dispatch(1); + + my $result = $client->get_endpoint('events')->Subscribe( { + ConsumerReference => { # WSNotification::Types::EndpointReferenceType + Address => { value => 'http://' . $localaddr . '/ref_'. $ref_id . '/' }, + # ReferenceParameters => { # WSNotification::Types::ReferenceParametersType + # }, + # Metadata => { # WSNotification::Types::MetadataType + # }, + }, + Filter => { # WSNotification::Types::FilterType + TopicExpression => { # WSNotification::Types::TopicExpressionType + xmlattr => { + Dialect => "http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet", + }, + value => $topic_str, + }, + # MessageContent => { # WSNotification::Types::QueryExpressionType + # }, + }, + InitialTerminationTime => xs_duration($duration), # AbsoluteOrRelativeTimeType + # SubscriptionPolicy => { + # }, + },, + ); + + die $result if not $result; + # print $result . "\n"; + + ### build Subscription Manager + my $submgr_addr = $result->get_SubscriptionReference()->get_Address()->get_value(); + Info("Subscription Manager at $submgr_addr"); + + my $serializer = $client->service('device', 'ep')->get_serializer(); + + my $submgr_svc = WSNotification::Interfaces::WSBaseNotificationSender::SubscriptionManagerPort->new({ + serializer => $serializer, + proxy => $submgr_addr, + }); + + return $submgr_svc; +} # end sub subscribe + +sub unsubscribe { + my ($submgr_svc) = @_; + + $submgr_svc->Unsubscribe( { },, ); +} + +sub renew { + my ($submgr_svc, $duration) = @_; + + my $result = $submgr_svc->Renew( { + TerminationTime => xs_duration($duration), # AbsoluteOrRelativeTimeType + },, + ); + die $result if not $result; +} + +sub events { + my ($localip, $localport) = @_; + + my $zm = _ZoneMinder->new(); + $zm->init(); + $zm->loadMonitors(); # call before fork() + + my %monitors = $zm->monitors(); + my $monitor_count = scalar keys(%monitors); + if ( $monitor_count == 0 ) { + Warning('No active ONVIF monitors found. Exiting'); + return; + } + Debug("Found $monitor_count active ONVIF monitors"); + Info('ONVIF Trigger daemon starting'); + + if ( !defined $localip ) { + #$localip = '192.168.0.2'; + #$localport = '0'; + } + + # re-use local address/port + # @LWP::Protocol::http::EXTRA_SOCK_OPTS = + *LWP::Protocol::http::_extra_sock_opts = sub { + # print "### extra_sock_opts ########################################\n"; + @EXTRA_SOCK_OPTS; + }; + + #*LWP::Protocol::http::_check_sock = sub + #{ + # my($self, $req, $sock) = @_; + # print "### check_sock ########################################\n"; + # dump($sock); + #}; + + my $daemon = start_daemon($localip, $localport, $zm); + my $port = $daemon->url; + $port =~ s|^.*:||; + $port =~ s|/.*$||; + my $localaddr = $localip . ':' . $port; + + Info('Daemon uses local address '.$localaddr); + + # This value is passed as the LocalAddr argument to IO::Socket::INET. + my $transport = SOAP::Transport::HTTP::Client->new( + # 'local_address' => $localaddr ); ## REUSE port + local_address => $localip ); + + foreach my $monitor (values(%monitors)) { + + my $client = $monitor->{onvif_client}; + my $event_svc = $client->get_endpoint('events'); + $event_svc->set_transport($transport); + # print "Sending from local address " . + # $event_svc->get_transport()->local_address . "\n"; + + my $submgr_svc = subscribe( + $client, $localaddr, 'tns1:RuleEngine//.', + SUBSCRIPTION_RENEW_INTERVAL, $monitor->{Id}); + + if ( !$submgr_svc ) { + Warning('Subscription failed for monitor #'.$monitor->{Id}); + next; + } + + $monitor->{submgr_svc} = $submgr_svc; + } # end foreach monitor + + while (1) { + Info('Sleeping for ' . (SUBSCRIPTION_RENEW_INTERVAL - SUBSCRIPTION_RENEW_EARLY).' seconds'); + sleep(SUBSCRIPTION_RENEW_INTERVAL - SUBSCRIPTION_RENEW_EARLY); + Info('Renewal'); + my %monitors = $zm->monitors(); + foreach my $monitor (values(%monitors)) { + if ( defined $monitor->{submgr_svc} ) { + renew($monitor->{submgr_svc}, SUBSCRIPTION_RENEW_INTERVAL + SUBSCRIPTION_RENEW_EARLY); + } + } + }; + + Info('ONVIF Trigger daemon exited'); + + %monitors = $zm->monitors(); + foreach my $monitor (values(%monitors)) { + if ( defined $monitor->{submgr_svc} ) { + unsubscribe($monitor->{submgr_svc}); + } + } +} # end sub events + +# ======================================================================== +# options processing + +sub HELP_MESSAGE { + my ($fh, $pkg, $ver, $opts) = @_; + print $fh "Usage: " . __FILE__ . " \n"; + print $fh <'zm_onvif-trigger'); +logSetSignal(); + +if ( !GetOptions( + 'local-addr|l=s' => \$localaddr, + 'script|s=s' => \$script, + 'verbose|v=s' => \$verbose, + )) { + HELP_MESSAGE(\*STDOUT); + exit(1); +} + +if ( defined $localaddr ) { + if ( $localaddr =~ /(.*):(.*)/ ) { + ($localip, $localport) = ($1, $2); + } else { + $localip = $localaddr; + $localport = '0'; + } +} + +events($localip, $localport); + +1; +__END__ + +=head1 NAME + +zmonvif-trigger.pl - ZoneMinder ONVIF trigger daemon + +=head1 SYNOPSIS + + zmonfig-trigger.pl [-v] [-s] [-l=] + + +=head1 DESCRIPTION + + +=head1 OPTIONS + + local-addr - local address to bind to + script|s=s - script to run + verbose|v=s - increase verbosity + +=cut + diff --git a/scripts/zmpkg.pl.in b/scripts/zmpkg.pl.in index 4b2032a8d..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,12 +170,21 @@ if ( $command =~ /^(?:start|restart)$/ ) { if ( $Config{ZM_DYN_DB_VERSION} and ( $Config{ZM_DYN_DB_VERSION} ne ZM_VERSION ) ) { - Fatal('Version mismatch, system is version '.ZM_VERSION - .', database is '.$Config{ZM_DYN_DB_VERSION} - .', please run zmupdate.pl to update.' - ); - exit(-1); - } + my ( $db_major, $db_minor, $db_micro ) = split(/\./, $Config{ZM_DYN_DB_VERSION}); + my ( $major, $minor, $micro ) = split(/\./, ZM_VERSION); + if ( $db_major != $major or $db_minor != $minor ) { + Fatal('Version mismatch, system is version '.ZM_VERSION + .', database is '.$Config{ZM_DYN_DB_VERSION} + .', please run zmupdate.pl to update.' + ); + exit(-1); + } else { + Error('Version mismatch, system is version '.ZM_VERSION + .', database is '.$Config{ZM_DYN_DB_VERSION} + .', please run zmupdate.pl to update.' + ); + } + } # end if version mismatch # Recreate the temporary directory if it's been wiped verifyFolder('@ZM_TMPDIR@'); @@ -216,9 +224,6 @@ if ( $command =~ /^(?:start|restart)$/ ) { } else { runCommand("zmdc.pl start zmc -m $monitor->{Id}"); } - if ( $monitor->{Function} ne 'Monitor' ) { - runCommand("zmdc.pl start zma -m $monitor->{Id}"); - } if ( $Config{ZM_OPT_CONTROL} ) { if ( $monitor->{Controllable} ) { runCommand("zmdc.pl start zmcontrol.pl --id $monitor->{Id}"); @@ -289,6 +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/zmrecover.pl.in b/scripts/zmrecover.pl.in index 58647b946..d6fe578f6 100644 --- a/scripts/zmrecover.pl.in +++ b/scripts/zmrecover.pl.in @@ -224,6 +224,7 @@ Debug("@Monitors"); $$Event{RelativePath} = join('/', $day_dir, $event_path); $$Event{Scheme} = 'Deep'; $$Event{Name} = "Event $event_id recovered"; + $$Event{StateId} = 1; $Event->MonitorId( $monitor_dir ); $Event->StorageId( $Storage->Id() ); $Event->DiskSpace( undef ); @@ -231,7 +232,7 @@ Debug("@Monitors"); $Event->Height( $Monitor->Height() ); $Event->Orientation( $Monitor->Orientation() ); $Event->recover_timestamps(); - if ( $$Event{StartTime} ) { + if ( $$Event{StartDateTime} ) { $Event->save({}, 1); Info("Event resurrected as " . $Event->to_string() ); } else { @@ -287,6 +288,7 @@ Debug("@Monitors"); $$Event{RelativePath} = join('/', $day_dir, $event_dir); $$Event{Scheme} = 'Deep'; $$Event{Name} = "Event $event_id recovered"; + $$Event{StateId} = 1; $Event->MonitorId( $monitor_dir ); $Event->Width( $Monitor->Width() ); $Event->Height( $Monitor->Height() ); @@ -294,7 +296,7 @@ Debug("@Monitors"); $Event->StorageId( $Storage->Id() ); $Event->DiskSpace( undef ); $Event->recover_timestamps(); - if ( $$Event{StartTime} ) { + if ( $$Event{StartDateTime} ) { $Event->save({}, 1); Info("Event resurrected as " . $Event->to_string() ); } else { @@ -309,13 +311,13 @@ Debug("@Monitors"); my ( undef, $year, $month, $day ) = split('/', $day_dir); $year += 2000; my ( $hour, $minute, $second ) = split('/', $event_dir); - my $StartTime =sprintf('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', $year, $month, $day, $hour, $minute, $second); + my $StartDateTime =sprintf('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', $year, $month, $day, $hour, $minute, $second); my $Event = ZoneMinder::Event->find_one( MonitorId=>$monitor_dir, - StartTime=>$StartTime, + StartDateTime=>$StartDateTime, ); if ( $Event ) { - Debug("Found event matching starttime on monitor $monitor_dir at $StartTime: " . $Event->to_string()); + Debug("Found event matching starttime on monitor $monitor_dir at $StartDateTime: " . $Event->to_string()); next; } @@ -352,13 +354,14 @@ Debug("@Monitors"); $$Event{Scheme} = 'Medium'; $$Event{RelativePath} = $event_dir; $$Event{Name} = "Event $event_id recovered"; + $$Event{StateId} = 1; $Event->MonitorId( $monitor_dir ); $Event->Width( $Monitor->Width() ); $Event->Height( $Monitor->Height() ); $Event->Orientation( $Monitor->Orientation() ); $Event->StorageId( $Storage->Id() ); $Event->recover_timestamps(); - if ( $$Event{StartTime} ) { + if ( $$Event{StartDateTime} ) { $Event->save({}, 1); Info("Event resurrected as " . $Event->to_string() ); } else { @@ -393,6 +396,7 @@ Debug("@Monitors"); if ( confirm() ) { $$Event{Scheme} = 'Shallow'; $$Event{Name} = "Event $event recovered"; + $$Event{StateId} = 1; #$$Event{Path} = $event_path; $Event->MonitorId( $monitor_dir ); $Event->Width( $Monitor->Width() ); @@ -400,7 +404,7 @@ Debug("@Monitors"); $Event->Orientation( $Monitor->Orientation() ); $Event->StorageId( $Storage->Id() ); $Event->recover_timestamps(); - if ( $$Event{StartTime} ) { + if ( $$Event{StartDateTime} ) { $Event->save({}, 1); Info("Event resurrected as " . $Event->to_string() ); } else { diff --git a/scripts/zmstats.pl.in b/scripts/zmstats.pl.in index 044f7e1ce..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()) ) { @@ -43,13 +51,50 @@ while( 1 ) { } } - $dbh->do('DELETE FROM Events_Hour WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 hour)') or Error($dbh->errstr()); - $dbh->do('DELETE FROM Events_Day WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 day)') or Error($dbh->errstr()); - $dbh->do('DELETE FROM Events_Week WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 week)') or Error($dbh->errstr()); - $dbh->do('DELETE FROM Events_Month WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 month)') or Error($dbh->errstr()); + $dbh->do('DELETE FROM Events_Hour WHERE StartDateTime < DATE_SUB(NOW(), INTERVAL 1 hour)') or Error($dbh->errstr()); + $dbh->do('DELETE FROM Events_Day WHERE StartDateTime < DATE_SUB(NOW(), INTERVAL 1 day)') or Error($dbh->errstr()); + $dbh->do('DELETE FROM Events_Week WHERE StartDateTime < DATE_SUB(NOW(), INTERVAL 1 week)') or Error($dbh->errstr()); + $dbh->do('DELETE FROM Events_Month WHERE StartDateTime < DATE_SUB(NOW(), INTERVAL 1 month)') or Error($dbh->errstr()); + + # Prune the Logs table if required + if ( $Config{ZM_LOG_DATABASE_LIMIT} ) { + if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^\d+$/ ) { + # Number of rows + my $selectLogRowCountSql = 'SELECT count(*) AS `Rows` FROM `Logs`'; + my $selectLogRowCountSth = $dbh->prepare_cached( $selectLogRowCountSql ) + or Fatal("Can't prepare '$selectLogRowCountSql': ".$dbh->errstr()); + my $res = $selectLogRowCountSth->execute() + or Fatal("Can't execute: ".$selectLogRowCountSth->errstr()); + my $row = $selectLogRowCountSth->fetchrow_hashref(); + my $logRows = $row->{Rows}; + if ( $logRows > $Config{ZM_LOG_DATABASE_LIMIT} ) { + 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 + + # 7 days is invalid. We need to remove the s + if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^(.*)s$/ ) { + $Config{ZM_LOG_DATABASE_LIMIT} = $1; + } + my $rows; + do { + $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 + + 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/zmtelemetry.pl.in b/scripts/zmtelemetry.pl.in index efd5e735e..c164df7e4 100644 --- a/scripts/zmtelemetry.pl.in +++ b/scripts/zmtelemetry.pl.in @@ -23,6 +23,7 @@ use strict; use bytes; +use utf8; @EXTRA_PERL_LIB@ use ZoneMinder; @@ -34,6 +35,7 @@ use Sys::MemInfo qw(totalmem); use Sys::CPU qw(cpu_count); use POSIX qw(strftime uname); use JSON::MaybeXS; +use Encode; $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; @@ -43,6 +45,7 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $help = 0; my $force = 0; +my $show = 0; # Interval between version checks my $interval; my $version; @@ -50,6 +53,7 @@ my $version; GetOptions( force => \$force, help => \$help, + show => \$show, interval => \$interval, version => \$version ); @@ -57,6 +61,14 @@ if ( $version ) { print( ZoneMinder::Base::ZM_VERSION . "\n"); exit(0); } +if ($show) { + my %telemetry; + my $dbh = zmDbConnect(); + collectData($dbh, \%telemetry); + my $result = jsonEncode(\%telemetry); + print ($result); + exit(0); +} if ( $help ) { pod2usage(-exitstatus => -1); } @@ -87,21 +99,9 @@ while( 1 ) { my $dbh = zmDbConnect(); # Build the telemetry hash # We should keep *BSD systems in mind when calling system commands + my %telemetry; - $telemetry{uuid} = getUUID($dbh); - @telemetry{qw(city region country latitude longitude)} = getGeo(); - $telemetry{timestamp} = strftime('%Y-%m-%dT%H:%M:%S%z', localtime()); - $telemetry{monitor_count} = countQuery($dbh,'Monitors'); - $telemetry{event_count} = countQuery($dbh,'Events'); - $telemetry{architecture} = runSysCmd('uname -p'); - ($telemetry{kernel}, $telemetry{distro}, $telemetry{version}) = getDistro(); - $telemetry{zm_version} = ZoneMinder::Base::ZM_VERSION; - $telemetry{system_memory} = totalmem(); - $telemetry{processor_count} = cpu_count(); - $telemetry{monitors} = getMonitorRef($dbh); - - Info('Sending data to ZoneMinder Telemetry server.'); - + collectData($dbh,\%telemetry); my $result = jsonEncode(\%telemetry); if ( sendData($result) ) { @@ -124,6 +124,24 @@ print 'ZoneMinder Telemetry Agent exiting at '.strftime('%y/%m/%d %H:%M:%S', loc # SUBROUTINES # ############### +# collect data to send +sub collectData { + my $dbh = shift; + my $telemetry = shift; + $telemetry->{uuid} = getUUID($dbh); + ($telemetry->{city},$telemetry->{region},$telemetry->{country},$telemetry->{latitude},$telemetry->{longitude})=getGeo(); + $telemetry->{timestamp} = strftime('%Y-%m-%dT%H:%M:%S%z', localtime()); + $telemetry->{monitor_count} = countQuery($dbh,'Monitors'); + $telemetry->{event_count} = countQuery($dbh,'Events'); + $telemetry->{architecture} = runSysCmd('uname -p'); + ($telemetry->{kernel}, $telemetry->{distro}, $telemetry->{version}) = getDistro(); + $telemetry->{zm_version} = ZoneMinder::Base::ZM_VERSION; + $telemetry->{system_memory} = totalmem(); + $telemetry->{processor_count} = cpu_count(); + $telemetry->{use_event_server} = $Config{ZM_OPT_USE_EVENTNOTIFICATION}; + $telemetry->{monitors} = getMonitorRef($dbh); +} + # Find, verify, then run the supplied system command sub runSysCmd { my $msg = shift; @@ -166,7 +184,7 @@ sub sendData { $req->header('content-length' => length($msg)); $req->header('connection' => 'Close'); - $req->content($msg); + $req->content(encode('UTF-8',$msg)); my $resp = $ua->request($req); my $resp_msg = $resp->decoded_content; @@ -196,7 +214,7 @@ sub getUUID { $uuid = $Config{ZM_TELEMETRY_UUID} = $sth->fetchrow_array(); $sth->finish(); - $sql = q`UPDATE Config set Value = ? WHERE Name = 'ZM_TELEMETRY_UUID'`; + $sql = q`UPDATE Config SET Value = ? WHERE Name = 'ZM_TELEMETRY_UUID'`; $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); $res = $sth->execute( "$uuid" ) or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); @@ -232,9 +250,9 @@ sub countQuery { my $dbh = shift; my $table = shift; - my $sql = "SELECT count(*) FROM $table"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my $sql = "SELECT count(*) FROM `$table`"; + my $sth = $dbh->prepare_cached($sql) or die "Can't prepare '$sql': ".$dbh->errstr(); + my $res = $sth->execute() or die 'Can\'t execute: '.$sth->errstr(); my $count = $sth->fetchrow_array(); $sth->finish(); @@ -245,7 +263,10 @@ sub countQuery { sub getMonitorRef { my $dbh = shift; - my $sql = 'SELECT Id,Name,Type,Function,Width,Height,Colours,MaxFPS,AlarmMaxFPS FROM Monitors'; + my $sql = 'SELECT `Id`,`Name`,`Type`,`Function`,`Width`,`Height`,`Colours`,`MaxFPS`,`AlarmMaxFPS`, + (SELECT Name FROM Manufacturers WHERE Manufacturers.Id = ManufacturerId), + (SELECT Name FROM Models WHERE Models.Id = ModelId) + FROM `Monitors`'; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); my $arrayref = $sth->fetchall_arrayref({}); @@ -363,7 +384,7 @@ zmtelemetry.pl - Send usage information to the ZoneMinder development team =head1 SYNOPSIS - zmtelemetry.pl [--force] [--help] [--interval=seconds] [--version] + zmtelemetry.pl [--force] [--help] [--show] [--interval=seconds] [--version] =head1 DESCRIPTION @@ -380,6 +401,7 @@ console under Options. --force Force the script to upload it's data instead of waiting for the defined interval since last upload. --help Display usage information + --show Displays telemetry data that is sent to zoneminder --interval Override the default configured interval since last upload. The value should be given in seconds, but can be an expression such as 24*60*60. diff --git a/scripts/zmtrack.pl.in b/scripts/zmtrack.pl.in index 58798e5de..10db09664 100644 --- a/scripts/zmtrack.pl.in +++ b/scripts/zmtrack.pl.in @@ -119,7 +119,6 @@ if ( !$monitor->{CanMoveMap} ) { } } -Debug("Found monitor for id '$monitor'"); exit(-1) if !zmMemVerify($monitor); sub Suspend { diff --git a/scripts/zmtrigger.pl.in b/scripts/zmtrigger.pl.in index b1743aeae..94f59e001 100644 --- a/scripts/zmtrigger.pl.in +++ b/scripts/zmtrigger.pl.in @@ -42,6 +42,7 @@ use constant SELECT_TIMEOUT => 0.25; @EXTRA_PERL_LIB@ use ZoneMinder; +use ZoneMinder::Monitor; use ZoneMinder::Trigger::Channel::Inet; use ZoneMinder::Trigger::Channel::Unix; use ZoneMinder::Trigger::Channel::Serial; @@ -87,6 +88,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 +126,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 ) { @@ -159,13 +167,9 @@ while (1) { foreach my $connection ( values(%spawned_connections) ) { if ( vec($rout, $connection->fileno(), 1) ) { Debug('Got input from spawned connection ' - .$connection->name() - .' (' - .$connection->fileno() - .')' - ); + .$connection->name().' ('.$connection->fileno().')'); my $messages = $connection->getMessages(); - if ( defined($messages) ) { + if (defined($messages)) { foreach my $message ( @$messages ) { handleMessage($connection, $message); } @@ -187,42 +191,36 @@ while (1) { } else { Fatal("Can't select: $!"); } - } # end if select returned activitiy + } # end if select returned activity # Check polled connections foreach my $connection ( @in_poll_connections ) { my $messages = $connection->getMessages(); - if ( defined($messages) ) { - foreach my $message ( @$messages ) { - handleMessage($connection, $message); - } + if (defined($messages)) { + foreach my $message (@$messages) { handleMessage($connection, $message) }; } } # Check for alarms that might have happened my @out_messages; foreach my $monitor ( values %monitors ) { - - if ( ! zmMemVerify($monitor) ) { + if (!$monitor->connect()) { # Our attempt to verify the memory handle failed. We should reload the monitors. # Don't need to zmMemInvalidate because the monitor reload will do it. + Debug("Failed connect, putting on reloads"); push @needsReload, $monitor; next; } - my ( $state, $last_event ) = zmMemRead( $monitor, - [ + my ($state, $last_event) = zmMemRead($monitor, [ 'shared_data:state', 'shared_data:last_event' - ] - ); + ]); -#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 +229,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; @@ -248,6 +246,7 @@ while (1) { } $monitor->{LastState} = $state; $monitor->{LastEvent} = $last_event; + $monitor->disconnect(); } # end foreach monitor foreach my $connection ( @out_connections ) { @@ -266,7 +265,7 @@ while (1) { Debug('Checking for timed actions'); my $now = time(); foreach my $action_time ( sort( grep { $_ < $now } @action_times ) ) { - Info("Found " . scalar @{$actions{$action_time}} . "actions expiring at $action_time"); + Info('Found '.(scalar @{$actions{$action_time}}).'actions expiring at '.$action_time); foreach my $action ( @{$actions{$action_time}} ) { my $connection = $action->{connection}; Info("Found action '$$action{message}'"); @@ -297,83 +296,80 @@ while (1) { # Reload all monitors from the dB every MONITOR_RELOAD_INTERVAL if ( (time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL ) { - foreach my $monitor ( values(%monitors) ) { - zmMemInvalidate( $monitor ); # Free up any used memory handle - } loadMonitors(); @needsReload = (); # We just reloaded all monitors so no need reload a specific monitor # If we have NOT just reloaded all monitors, reload a specific monitor if its shared mem changed - } elsif ( @needsReload ) { - foreach my $monitor ( @needsReload ) { - loadMonitor($monitor); + } elsif (@needsReload) { + foreach my $monitor (@needsReload) { + $monitor = $monitors{$monitor->Id()} = ZoneMinder::Monitor->find_one(Id=>$monitor->Id()); + if ( $$monitor{Function} eq 'None' ) { + delete $monitors{$monitor->Id()}; + } elsif ( $Config{ZM_SERVER_ID} and ($$monitor{ServerId} != $Config{ZM_SERVER_ID})) { + delete $monitors{$monitor->Id()}; + } else { + if ($monitor->connect()) { + $monitor->{LastState} = zmGetMonitorState($monitor); + $monitor->{LastEvent} = zmGetLastEvent($monitor); + } + } } @needsReload = (); } # zmDbConnect will ping and reconnect if neccessary $dbh = zmDbConnect(); -} # end while ( 1 ) +} # end while (!$zm_terminate) Info('Trigger daemon exiting'); exit; -sub loadMonitor { - my $monitor = shift; - - Debug("Loading monitor $monitor"); - zmMemInvalidate($monitor); - - if ( zmMemVerify($monitor) ) { # This will re-init shared memory - $monitor->{LastState} = zmGetMonitorState($monitor); - $monitor->{LastEvent} = zmGetLastEvent($monitor); - } -} # end sub loadMonitor - sub loadMonitors { - Debug('Loading monitors'); $monitor_reload_time = time(); - my %new_monitors = (); + %monitors = (); - my $sql = q`SELECT * FROM Monitors - WHERE find_in_set( Function, 'Modect,Mocord,Nodect' )`. - ( $Config{ZM_SERVER_ID} ? ' AND ServerId=?' : '' ) - ; - my $sth = $dbh->prepare_cached( $sql ) - 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() ) { - if ( zmMemVerify($monitor) ) { # This will re-init shared memory + foreach my $monitor ( ZoneMinder::Monitor->find( + Function=>['Modect','Mocord','Nodect','Record'], + ($Config{ZM_SERVER_ID} ? (ServerId=>$Config{ZM_SERVER_ID}) : ()), + )) { + if ($monitor->connect()) { # This will re-init shared memory $monitor->{LastState} = zmGetMonitorState($monitor); $monitor->{LastEvent} = zmGetLastEvent($monitor); } - $new_monitors{$monitor->{Id}} = $monitor; + $monitors{$monitor->{Id}} = $monitor; } # end while fetchrow - %monitors = %new_monitors; -} +} # end sub loadMonitors sub handleMessage { my $connection = shift; my $message = shift; + # CUA - Axis camera send the message quoted with" + # CUA - Also Axis camera cannot save the plus sign which + $message =~ s/^\"//g; + $message =~ s/\"$//g; + my ( $id, $action, $score, $cause, $text, $showtext ) = split( /\|/, $message ); - $score = 0 if ( !defined($score) ); - $cause = '' if ( !defined($cause) ); - $text = '' if ( !defined($text) ); + $score = 0 if !defined($score); + $cause = '' if !defined($cause); + $text = '' if !defined($text); my $monitor = $monitors{$id}; if ( !$monitor ) { - Warning("Can't find monitor '$id' for message '$message'"); + loadMonitors(); + $monitor = $monitors{$id}; + if ( !$monitor ) { + Warning("Can't find monitor '$id' for message '$message'"); + return; + } + } + if ( !zmMemVerify($monitor) ) { + Warning("Can't verify monitor '$id' for message '$message'"); return; } - Debug("Found monitor for id '$id'"); - - next if !zmMemVerify($monitor); Debug("Handling action '$action'"); - if ( $action =~ /^(enable|disable)(?:\+(\d+))?$/ ) { + if ( $action =~ /^(enable|disable)(?:[\+ ](\d+))?$/ ) { my $state = $1; my $delay = $2; if ( $state eq 'enable' ) { @@ -383,21 +379,25 @@ sub handleMessage { } # Force a reload $monitor_reload_time = 0; - Info("Set monitor to $state"); + Info('Set monitor to '.$state); if ( $delay ) { - my $action_text = $id.'|'.( ($state eq 'enable') - ? 'disable' - : 'enable' - ); + my $action_text = $id.'|'.(($state eq 'enable') ? 'disable' : 'enable'); handleDelay($delay, $connection, $action_text); } } elsif ( $action =~ /^(on|off)(?:[ \+](\d+))?$/ ) { - next if !$monitor->{Enabled}; + if ( !$monitor->{Enabled} ) { + Info('Motion detection not enabled on monitor '.$id); + return; + } my $trigger = $1; my $delay = $2; my $trigger_data; if ( $trigger eq 'on' ) { + if ( $score <= 0 ) { + Warning('Triggering on with invalid score will have no result.'); + return; + } zmTriggerEventOn($monitor, $score, $cause, $text); zmTriggerShowtext($monitor, $showtext) if defined($showtext); Info("Trigger '$trigger' '$cause'"); diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index b0b63757a..f62b14316 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -2,7 +2,7 @@ # # ========================================================================== # -# ZoneMinder Update Script, $Date$, $Revision$ +# ZoneMinder Update Script # Copyright (C) 2001-2008 Philip Coombes # # This program is free software; you can redistribute it and/or @@ -27,32 +27,34 @@ 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 -This script just checks what the most recent release of ZoneMinder is -at the the moment. It will eventually be responsible for applying and -configuring upgrades etc, including on the fly upgrades. +This script checks what the most recent release of ZoneMinder is +at the the moment by downloading https://update.zoneminder.com/version.txt. +It can also apply and configure upgrades etc, including on the fly upgrades. =head1 OPTIONS --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 --interactive - interact with the user --nointeractive - do not interact with the user + -c, --check - Check for updated versions of ZoneMinder. + If not interactive zmupdate.pl will stay running, checking every hour. + If interactive will try once, print out result and quit. + -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 + -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 =cut use strict; +use warnings; use bytes; use version; -use Crypt::Eksblowfish::Bcrypt; -use Data::Entropy::Algorithms qw(rand_bits); # ========================================================================== # @@ -94,7 +96,7 @@ my $use_log = (($> == 0) || ($> == $web_uid)); logInit( toFile=>$use_log?DEBUG:NOLOG ); logSetSignal(); -my $interactive = 1; +my $interactive = -t STDERR; # interactive if we have IO my $check = 0; my $freshen = 0; my $rename = 0; @@ -103,6 +105,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,29 +118,36 @@ GetOptions( 'interactive!' =>\$interactive, 'user:s' =>\$dbUser, 'pass:s' =>\$dbPass, + 'super' =>\$super, 'dir:s' =>\$updateDir ) or pod2usage(-exitstatus => -1); my $dbh = zmDbConnect(undef, { mysql_multi_statements=>1 } ); +die "Unable to connect to db\n" if !$dbh; + $Config{ZM_DB_USER} = $dbUser; $Config{ZM_DB_PASS} = $dbPass; +# we escape dbpass with single quotes so that $ in the password has no effect, but dbpass could have a ' in it. +$dbPass =~ s/'/\\'/g; if ( ! ($check || $freshen || $rename || $zoneFix || $migrateEvents || $version) ) { if ( $Config{ZM_DYN_DB_VERSION} ) { $version = $Config{ZM_DYN_DB_VERSION}; } else { - print( STDERR "Please give a valid option\n" ); + print(STDERR "Please give a valid option\n"); pod2usage(-exitstatus => -1); } } if ( ($check + $freshen + $rename + $zoneFix + $migrateEvents + ($version?1:0)) > 1 ) { - print( STDERR "Please give only one option\n" ); + print(STDERR "Please give only one option\n"); pod2usage(-exitstatus => -1); } -if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) { - print( "Update agent starting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); +if ($check) { + if (!$interactive) { + Info('Update agent starting at '.strftime('%y/%m/%d %H:%M:%S', localtime() )."\n"); + } my $currVersion = $Config{ZM_DYN_CURR_VERSION}; my $lastVersion = $Config{ZM_DYN_LAST_VERSION}; @@ -145,89 +155,84 @@ if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) { if ( !$currVersion ) { $currVersion = $Config{ZM_VERSION}; - - my $sql = "update Config set Value = ? where Name = 'ZM_DYN_CURR_VERSION'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( "$currVersion" ) or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); + zmDbDo("UPDATE `Config` SET `Value` = ? WHERE `Name` = 'ZM_DYN_CURR_VERSION'", $currVersion); } - while( 1 ) { + while ( 1 ) { my $now = time(); - if ( !$lastVersion || !$lastCheck || (($now-$lastCheck) > CHECK_INTERVAL) ) { - Info( "Checking for updates\n" ); + if ( !$interactive and $lastVersion and $lastCheck and (($now-$lastCheck) <= CHECK_INTERVAL) ) { + Debug("Not checking for updates since we already have less than " . CHECK_INTERVAL . " seconds ago."); + } else { + Info('Checking for updates'); use LWP::UserAgent; my $ua = LWP::UserAgent->new; - $ua->agent( "ZoneMinder Update Agent/".ZM_VERSION ); + $ua->agent('ZoneMinder Update Agent/'.ZM_VERSION); if ( $Config{ZM_UPDATE_CHECK_PROXY} ) { - $ua->proxy( 'http', $Config{ZM_UPDATE_CHECK_PROXY} ); + $ua->proxy('http', $Config{ZM_UPDATE_CHECK_PROXY}); } - my $req = HTTP::Request->new( GET=>'https://update.zoneminder.com/version.txt' ); + my $req = HTTP::Request->new(GET=>'https://update.zoneminder.com/version.txt'); my $res = $ua->request($req); if ( $res->is_success ) { - $lastVersion = $res->content; - chomp($lastVersion); + my $latestVersion = $res->content; + chomp($latestVersion); $lastCheck = $now; - Info( "Got version: '".$lastVersion."'\n" ); + Info('Got version: '.$latestVersion); - my $lv_sql = "update Config set Value = ? where Name = 'ZM_DYN_LAST_VERSION'"; - my $lv_sth = $dbh->prepare_cached( $lv_sql ) or die( "Can't prepare '$lv_sql': ".$dbh->errstr() ); - my $lv_res = $lv_sth->execute( $lastVersion ) or die( "Can't execute: ".$lv_sth->errstr() ); - $lv_sth->finish(); - - my $lc_sql = "update Config set Value = ? where Name = 'ZM_DYN_LAST_CHECK'"; - my $lc_sth = $dbh->prepare_cached( $lc_sql ) or die( "Can't prepare '$lc_sql': ".$dbh->errstr() ); - my $lc_res = $lc_sth->execute( $lastCheck ) or die( "Can't execute: ".$lc_sth->errstr() ); - $lc_sth->finish(); + zmDbDo('UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_VERSION\'', $latestVersion); + zmDbDo('UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_CHECK\'', $lastCheck); + if ($interactive) { + print("Last version $lastVersion, Latest version $latestVersion, our version " . ZM_VERSION."\n"); + exit(0); + } } else { - Error( "Error check failed: '".$res->status_line()."'\n" ); + Error('Error check failed: \''.$res->status_line().'\''); } } - sleep( 3600 ); + sleep(3600); } - print( "Update agent exiting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); + print('Update agent exiting at '.strftime('%y/%m/%d %H:%M:%S', localtime())."\n"); } if ( $rename ) { require File::Find; - chdir( EVENT_PATH ); + chdir(EVENT_PATH); sub renameImage { my $file = $_; # Ignore directories if ( -d $file ) { - print( "Checking directory '$file'\n" ); + print("Checking directory '$file'\n"); return; } if ( $file !~ /(capture|analyse)-(\d+)(\.jpg)/ ) { return; } - my $newFile = "$2-$1$3"; + my $newFile = $2.'-'.$1.$3; - print( "Renaming '$file' to '$newFile'\n" ); - rename( $file, $newFile ) or warn( "Can't rename '$file' to '$newFile'" ); + print("Renaming '$file' to '$newFile'\n"); + rename($file, $newFile) or warn("Can't rename '$file' to '$newFile'"); } - File::Find::find( \&renameImage, '.' ); -} -if ( $zoneFix ) { + File::Find::find(\&renameImage, '.'); +} # end if rename - my $sql = "select Z.*, M.Width as MonitorWidth, M.Height as MonitorHeight from Zones as Z inner join Monitors as M on Z.MonitorId = M.Id where Z.Units = 'Percent'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); +if ( $zoneFix ) { + my $sql = "SELECT Z.*, M.Width AS MonitorWidth, M.Height AS MonitorHeight FROM Zones AS Z INNER JOIN Monitors AS M ON Z.MonitorId = M.Id WHERE Z.Units = 'Percent'"; + my $sth = $dbh->prepare_cached($sql) or die("Can't prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute() or die("Can't execute: ".$sth->errstr()); my @zones; - while( my $zone = $sth->fetchrow_hashref() ) { - push( @zones, $zone ); + while ( my $zone = $sth->fetchrow_hashref() ) { + push(@zones, $zone); } $sth->finish(); - $sql = 'update Zones set MinAlarmPixels = ?, MaxAlarmPixels = ?, MinFilterPixels = ?, MaxFilterPixels = ?, MinBlobPixels = ?, MaxBlobPixels = ? where Id = ?'; - $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + $sql = 'UPDATE Zones SET MinAlarmPixels = ?, MaxAlarmPixels = ?, MinFilterPixels = ?, MaxFilterPixels = ?, MinBlobPixels = ?, MaxBlobPixels = ? WHERE Id = ?'; + $sth = $dbh->prepare_cached($sql) or die("Can't prepare '$sql': ".$dbh->errstr()); foreach my $zone ( @zones ) { my $zone_width = (($zone->{HiX}*$zone->{MonitorWidth})-($zone->{LoX}*$zone->{MonitorWidth}))/100; my $zone_height = (($zone->{HiY}*$zone->{MonitorHeight})-($zone->{LoY}*$zone->{MonitorHeight}))/100; @@ -241,78 +246,84 @@ if ( $zoneFix ) { ($zone->{MinBlobPixels}*$monitor_area)/$zone_area, ($zone->{MaxBlobPixels}*$monitor_area)/$zone_area, $zone->{Id} - ) or die( "Can't execute: ".$sth->errstr() ); + ) or die("Can't execute: ".$sth->errstr()); } $sth->finish(); -} +} # end if zoneFix + if ( $migrateEvents ) { - my $webUid = (getpwnam( $Config{ZM_WEB_USER} ))[2]; - my $webGid = (getgrnam( $Config{ZM_WEB_USER} ))[2]; + my $webUid = (getpwnam($Config{ZM_WEB_USER}))[2]; + my $webGid = (getgrnam($Config{ZM_WEB_USER}))[2]; if ( !(($> == 0) || ($> == $webUid)) ) { - print( "Error, migrating events can only be done as user root or ".$Config{ZM_WEB_USER}.".\n" ); - exit( -1 ); + print("Error, migrating events can only be done as user root or ".$Config{ZM_WEB_USER}.".\n"); + exit(-1); } # Run as web user/group $( = $webGid; - $) = $webGid; + $) = $webGid; $< = $webUid; $> = $webUid; - print( "\nAbout to convert saved events to deep storage, please ensure that ZoneMinder is fully stopped before proceeding.\nThis process is not easily reversible. Are you sure you wish to proceed?\n\nPress 'y' to continue or 'n' to abort : " ); + print(' +About to convert saved events to deep storage, please ensure that ZoneMinder is fully stopped before proceeding. +This process is not easily reversible. Are you sure you wish to proceed? + +Press \'y\' to continue or \'n\' to abort : '); my $response = ; - chomp( $response ); + chomp($response); while ( $response !~ /^[yYnN]$/ ) { - print( "Please press 'y' to continue or 'n' to abort only : " ); + print("Please press 'y' to continue or 'n' to abort only : "); $response = ; - chomp( $response ); + chomp($response); } if ( $response =~ /^[yY]$/ ) { - print( "Converting all events to deep storage.\n" ); + print("Converting all events to deep storage.\n"); - chdir( $Config{ZM_PATH_WEB} ); - my $sql = "select *, unix_timestamp(StartTime) as UnixStartTime from Events"; - my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + chdir($Config{ZM_PATH_WEB}); + my $sql = 'SELECT *, unix_timestamp(StartTime) AS UnixStartTime FROM Events'; + my $sth = $dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute(); if ( !$res ) { - Fatal( "Can't fetch Events: ".$sth->errstr() ); + Fatal("Can't fetch Events: ".$sth->errstr()); } while( my $event = $sth->fetchrow_hashref() ) { my $oldEventPath = $Config{ZM_DIR_EVENTS}.'/'.$event->{MonitorId}.'/'.$event->{Id}; if ( !-d $oldEventPath ) { - print( "Warning, can't find old event path '$oldEventPath', already converted?\n" ); + print("Warning, can't find old event path '$oldEventPath', already converted?\n"); next; } - print( "Converting event ".$event->{Id}."\n" ); - my $newDatePath = $Config{ZM_DIR_EVENTS}.'/'.$event->{MonitorId}.'/'.strftime( "%y/%m/%d", localtime($event->{UnixStartTime}) ); - my $newTimePath = strftime( "%H/%M/%S", localtime($event->{UnixStartTime}) ); + print('Converting event '.$event->{Id}."\n"); + my $newDatePath = $Config{ZM_DIR_EVENTS}.'/'.$event->{MonitorId}.'/'.strftime('%y/%m/%d', localtime($event->{UnixStartTime})); + my $newTimePath = strftime('%H/%M/%S', localtime($event->{UnixStartTime})); my $newEventPath = $newDatePath.'/'.$newTimePath; ( my $truncEventPath = $newEventPath ) =~ s|/\d+$||; - makePath( $truncEventPath, $Config{ZM_PATH_WEB} ); + makePath($truncEventPath, $Config{ZM_PATH_WEB}); my $idLink = $newDatePath.'/.'.$event->{Id}; - symlink( $newTimePath, $idLink ) or die( "Can't symlink $newTimePath -> $idLink: $!" ); - rename( $oldEventPath, $newEventPath ) or die( "Can't move $oldEventPath -> $newEventPath: $!" ); + symlink($newTimePath, $idLink) or die("Can't symlink $newTimePath -> $idLink: $!"); + rename($oldEventPath, $newEventPath) or die("Can't move $oldEventPath -> $newEventPath: $!"); } $sth->finish(); - print( "Updating configuration.\n" ); + print("Updating configuration.\n"); $sql = "UPDATE `Config` SET `Value` = ? WHERE `Name` = 'ZM_USE_DEEP_STORAGE'"; - $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute( 1 ) or die( "Can't execute: ".$sth->errstr() ); + $sth = $dbh->prepare_cached($sql) or die( "Can't prepare '$sql': ".$dbh->errstr()); + $res = $sth->execute(1) or die("Can't execute: ".$sth->errstr()); $sth->finish(); - print( "All events converted.\n\n" ); + print("All events converted.\n\n"); } else { - print( "Aborting event conversion.\n\n" ); + print("Aborting event conversion.\n\n"); } } + if ( $freshen ) { - print( "\nFreshening configuration in database\n" ); + print("\nFreshening configuration in database\n"); migratePaths(); migratePasswords(); ZoneMinder::Config::loadConfigFromDB(); @@ -324,8 +335,8 @@ if ( $interactive ) { # Now check for MyISAM Tables my @MyISAM_Tables; my $sql = "SELECT `table_name` FROM INFORMATION_SCHEMA.TABLES WHERE `table_schema`='zm' AND `engine` = 'MyISAM'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my $sth = $dbh->prepare_cached($sql) or die("Can't prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute() or die("Can't execute: ".$sth->errstr()); while( my $dbTable = $sth->fetchrow() ) { push @MyISAM_Tables, $dbTable; @@ -333,17 +344,22 @@ if ( $interactive ) { $sth->finish(); if ( @MyISAM_Tables ) { - print( "\nPrevious versions of ZoneMinder used the MyISAM database engine.\nHowever, the recommended database engine is InnoDB.\n"); - print( "\nHint: InnoDB tables are much less likely to be corrupted during an unclean shutdown.\n\nPress 'y' to convert your tables to InnoDB or 'n' to skip : "); + print(' +Previous versions of ZoneMinder used the MyISAM database engine. +However, the recommended database engine is InnoDB. + +Hint: InnoDB tables are much less likely to be corrupted during an unclean shutdown. + +Press \'y\' to convert your tables to InnoDB or \'n\' to skip : '); my $response = ; chomp( $response ); if ( $response =~ /^[yY]$/ ) { $dbh->do(q|SET sql_mode='traditional'|); # Elevate warnings to errors - print "\nConverting MyISAM tables to InnoDB. Please wait.\n"; - foreach (@MyISAM_Tables) { + print "\nConverting MyISAM tables to InnoDB. Please wait.\n"; + foreach ( @MyISAM_Tables ) { my $sql = "ALTER TABLE `$_` ENGINE = InnoDB"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my $sth = $dbh->prepare_cached($sql) or die("Can't prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute() or die("Can't execute: ".$sth->errstr()); $sth->finish(); } $dbh->do(q|SET sql_mode=''|); # Set mode back to default @@ -360,45 +376,56 @@ if ( $version ) { exit(0); } - print( "\nInitiating database upgrade to version ".ZM_VERSION." from version $version\n" ); + my $start_zm = 0; + print("\nInitiating database upgrade to version ".ZM_VERSION." from version $version\n"); if ( $interactive ) { - if ( $Config{ZM_DYN_DB_VERSION} && $Config{ZM_DYN_DB_VERSION} ne $version ) { - print( "\nWARNING - You have specified an upgrade from version $version but the database version found is ".$Config{ZM_DYN_DB_VERSION}.". Is this correct?\nPress enter to continue or ctrl-C to abort : " ); + if ( $Config{ZM_DYN_DB_VERSION} && ($Config{ZM_DYN_DB_VERSION} ne $version) ) { + print("\nWARNING - You have specified an upgrade from version $version but the database version found is $Config{ZM_DYN_DB_VERSION}. Is this correct?\nPress enter to continue or ctrl-C to abort : "); my $response = ; } - print( "\nPlease ensure that ZoneMinder is stopped on your system prior to upgrading the database.\nPress enter to continue or ctrl-C to stop : " ); - my $response = ; + if ( systemStatus() eq 'running' ) { + print"\nZoneMinder system appears to be running. While not strictly required, it is advised to stop ZM during the update process. Would you like to stop ZM now? [Yn]:"; + my $response = ; + chomp($response); + if ( $response !~ /Yy/ ) { + packageControl('stop'); + $start_zm = 1; + } + } - print( "\nDo you wish to take a backup of your database prior to upgrading?\nThis may result in a large file in @ZM_TMPDIR@ if you have a lot of events.\nPress 'y' for a backup or 'n' to continue : " ); - $response = ; - chomp( $response ); + print("\nDo you wish to take a backup of your database prior to upgrading?\nThis may result in a large file in @ZM_TMPDIR@ if you have a lot of events.\nPress 'y' for a backup or 'n' to continue : "); + my $response = ; + chomp($response); while ( $response !~ /^[yYnN]$/ ) { - print( "Please press 'y' for a backup or 'n' to continue only : " ); + print("Please press 'y' for a backup or 'n' to continue only : "); $response = ; - chomp( $response ); + chomp($response); } 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; + $command .= ' -S'.$portOrSocket; } else { - $command .= " -h".$host." -P".$portOrSocket; + $command .= ' -h'.$host.' -P'.$portOrSocket; } } else { - $command .= " -h".$host; + $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" ); - print( "Executing '$command'\n" ) if ( logDebugging() ); + 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"); + ($command) = $command =~ /(.*)/; # detaint + print("Executing '$command'\n") if logDebugging(); my $output = qx($command); my $status = $? >> 8; if ( $status || logDebugging() ) { @@ -417,11 +444,6 @@ if ( $version ) { print( "\nUpgrading database to version ".ZM_VERSION."\n" ); -# Update config first of all - migratePaths(); - ZoneMinder::Config::loadConfigFromDB(); - ZoneMinder::Config::saveConfigToDB(); - my $cascade = undef; if ( $cascade || $version eq "1.19.0" ) { # Patch the database @@ -847,9 +869,9 @@ if ( $version ) { } $cascade = !undef; } - if ( $cascade || $version eq "1.24.4" ) { + if ( $cascade || $version eq '1.24.4' ) { # Patch the database - patchDB( $dbh, "1.24.4" ); + patchDB($dbh, '1.24.4'); # Copy the FTP specific values to the new general config my $fetchSql = "select * from Config where Name like 'ZM_UPLOAD_FTP_%'"; @@ -863,12 +885,12 @@ if ( $version ) { } $cascade = !undef; } - if ( $cascade || $version lt "1.26.0" ) { - my $sth = $dbh->prepare_cached( 'select * from Monitors LIMIT 0,1' ); + if ( $cascade || $version lt '1.26.0' ) { + my $sth = $dbh->prepare_cached('SELECT * FROM Monitors LIMIT 0,1'); die "Error: " . $dbh->errstr . "\n" unless ($sth); die "Error: " . $sth->errstr . "\n" unless ($sth->execute); - my $columns = $sth->{'NAME'}; + my $columns = $sth->{NAME}; if ( ! grep(/^Colours$/, @$columns ) ) { $dbh->do(q{alter table Monitors add column `Colours` tinyint(3) unsigned NOT NULL default '1' after `Height`;}); } # end if @@ -898,28 +920,31 @@ if ( $version ) { die "Should have found upgrade scripts at $updateDir\n"; } # end if + my $sql = "UPDATE `Config` SET `Value` = ? WHERE `Name` = 'ZM_DYN_DB_VERSION'"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + foreach my $patch ( @files ) { my ( $v ) = $patch =~ /^zm_update\-([\d\.]+)\.sql$/; #PP make sure we use version compare - if ( version->parse('v' . $v) > version->parse('v' . $version) ) { - print( "Upgrading DB to $v from $version\n" ); - patchDB( $dbh, $v ); - my $sql = "update Config set Value = ? where Name = 'ZM_DYN_DB_VERSION'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $version ) or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); + if ( version->parse('v'.$v) > version->parse('v'.$version) ) { + print("Upgrading DB to $v from $version\n"); + if ( patchDB($dbh, $v) ) { + my $res = $sth->execute($version) or die( "Can't execute: ".$sth->errstr() ); + } #patchDB_using_do( $dbh, $version, $updateDir.'/'.$patch ); } # end if newer version } # end foreach patchfile + + $sth->finish(); $cascade = !undef; } # end if if ( $cascade ) { - my $installed_version = ZM_VERSION; - my $sql = 'update Config set Value = ? where Name = ?'; + # This is basically here so that we don't need zm-update-blah.sql files for versions without db changes + my $sql = 'UPDATE `Config` SET `Value` = ? WHERE `Name` = ?'; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( "$installed_version", 'ZM_DYN_DB_VERSION' ) or die( "Can't execute: ".$sth->errstr() ); - $res = $sth->execute( "$installed_version", 'ZM_DYN_CURR_VERSION' ) or die( "Can't execute: ".$sth->errstr() ); + $sth->execute(ZM_VERSION, 'ZM_DYN_DB_VERSION') or die( "Can't execute: ".$sth->errstr() ); + $sth->execute(ZM_VERSION, 'ZM_DYN_CURR_VERSION') or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); } else { zmDbDisconnect(); @@ -930,56 +955,64 @@ if ( $version ) { #my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); #my $res = $sth->execute( ) or die( "Can't execute: ".$sth->errstr() ); #$sth->finish(); - print( "\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n" ); -} + + print("\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n"); + if ( $start_zm ) { + print("Starting ZM since we stopped it for the update\n"); + packageControl('start'); + } +} # end if version + zmDbDisconnect(); -exit( 0 ); +exit(0); sub patchDB_using_do { my ( $dbh, $version, $file ) = @_; - open( my $fh, '<', $file ) or die "Unable to open $file $!"; + open(my $fh, '<', $file) or die "Unable to open $file $!"; $/ = undef; my $sql = <$fh>; close $fh; if ( $sql ) { - $dbh->{'AutoCommit'} = 0; + $dbh->{AutoCommit} = 0; $dbh->do($sql); if ( $dbh->errstr() ) { $dbh->rollback(); - die "Error: " . $dbh->errstr(). ". Rolled back.\n"; + die 'Error: '.$dbh->errstr().". Rolled back.\n"; } # end if error - my $sql = "update Config set Value = ? where Name = 'ZM_DYN_DB_VERSION'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $version ) or die( "Can't execute: ".$sth->errstr() ); + my $sql = 'UPDATE `Config` SET `Value` = ? WHERE `Name` = \'ZM_DYN_DB_VERSION\''; + my $sth = $dbh->prepare_cached($sql) or die "Can't prepare '$sql': ".$dbh->errstr(); + my $res = $sth->execute($version) or die 'Can\'t execute: '.$sth->errstr(); $sth->finish(); - $dbh->{'AutoCommit'} = 1; + $dbh->{AutoCommit} = 1; } else { Warning("Empty db update file at $file"); } -} +} # end sub patchDB_using_do + sub patchDB { my $dbh = shift; my $version = shift; - - my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); + 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; } else { $command .= ' -h'.$host.' -P'.$portOrSocket; } - } else { + } elsif ( $host ) { $command .= ' -h'.$host; } - if ( $dbUser ) { - $command .= ' -u'.$dbUser; - $command .= ' -p"'.$dbPass.'"' if $dbPass; - } $command .= ' '.$Config{ZM_DB_NAME}.' < '; if ( $updateDir ) { $command .= $updateDir; @@ -988,39 +1021,41 @@ sub patchDB { } $command .= '/zm_update-'.$version.'.sql'; - print( "Executing '$command'\n" ) if ( logDebugging() ); + print("Executing '$command'\n") if logDebugging(); + ($command) = $command =~ /(.*)/; # detaint my $output = qx($command); my $status = $? >> 8; if ( $status || logDebugging() ) { - chomp( $output ); - print( "Output: $output\n" ); + chomp($output); + print("Output: $output\n"); } if ( $status ) { - die( "Command '$command' exited with status: $status\n" ); + die("Command '$command' exited with status: $status\n"); } - print( "\nDatabase successfully upgraded to version $version.\n" ); - -} + print("\nDatabase successfully upgraded to version $version.\n"); +} # end sub patchDB sub migratePasswords { - print ("Migratings passwords, if any...\n"); - my $sql = "select * from Users"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - while( my $user = $sth->fetchrow_hashref() ) { - my $scheme = substr($user->{Password}, 0, 1); - if ($scheme eq "*") { - print ("-->".$user->{Username}. " password will be migrated\n"); - my $salt = Crypt::Eksblowfish::Bcrypt::en_base64(rand_bits(16*8)); - my $settings = '$2a$10$'.$salt; - my $pass_hash = Crypt::Eksblowfish::Bcrypt::bcrypt($user->{Password},$settings); - my $new_pass_hash = "-ZM-".$pass_hash; - $sql = "UPDATE Users SET PASSWORD=? WHERE Username=?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute($new_pass_hash, $user->{Username}) or die( "Can't execute: ".$sth->errstr() ); - } + use Crypt::Eksblowfish::Bcrypt; + use Data::Entropy::Algorithms qw(rand_bits); + print("Migratings passwords, if any...\n"); + my $sql = 'SELECT * FROM `Users`'; + my $sth = $dbh->prepare_cached($sql) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die("Can't execute: ".$sth->errstr()); + while ( my $user = $sth->fetchrow_hashref() ) { + my $scheme = substr($user->{Password}, 0, 1); + if ($scheme eq '*') { + print('-->'.$user->{Username}." password will be migrated\n"); + my $salt = Crypt::Eksblowfish::Bcrypt::en_base64(rand_bits(16*8)); + my $settings = '$2a$10$'.$salt; + my $pass_hash = Crypt::Eksblowfish::Bcrypt::bcrypt($user->{Password},$settings); + my $new_pass_hash = '-ZM-'.$pass_hash; + $sql = 'UPDATE Users SET `Password`=? WHERE `Username`=?'; + my $sth = $dbh->prepare_cached($sql) or die("Can't prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute($new_pass_hash, $user->{Username}) or die("Can't execute: ".$sth->errstr()); } -} + } +} # end sub migratePasswords sub migratePaths { diff --git a/scripts/zmvideo.pl.in b/scripts/zmvideo.pl.in index 04cfabb04..c676eb164 100644 --- a/scripts/zmvideo.pl.in +++ b/scripts/zmvideo.pl.in @@ -201,7 +201,7 @@ if ( $event_id ) { my $sql = " SELECT (SELECT max(Delta) FROM Frames WHERE EventId=Events.Id)-(SELECT min(Delta) FROM Frames WHERE EventId=Events.Id) as FullLength, Events.*, - unix_timestamp(Events.StartTime) as Time, + unix_timestamp(Events.StartDateTime) as Time, M.Name as MonitorName, M.Palette FROM Events diff --git a/scripts/zmwatch.pl.in b/scripts/zmwatch.pl.in index 1302039ee..dd5cfc252 100644 --- a/scripts/zmwatch.pl.in +++ b/scripts/zmwatch.pl.in @@ -56,6 +56,7 @@ use constant START_DELAY => 30; # To give everything else time to start @EXTRA_PERL_LIB@ use ZoneMinder; use ZoneMinder::Storage; +use ZoneMinder::Monitor; use POSIX; use DBI; use autouse 'Data::Dumper'=>qw(Dumper); @@ -66,104 +67,91 @@ $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; -logInit(); +my $log = 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); my $dbh = zmDbConnect(); -my $sql = $Config{ZM_SERVER_ID} ? 'SELECT * FROM Monitors WHERE ServerId=?' : 'SELECT * FROM Monitors'; -my $sth = $dbh->prepare_cached($sql) - or Fatal("Can't prepare '$sql': ".$dbh->errstr()); -while( 1 ) { - while ( ! ( $dbh and $dbh->ping() ) ) { - if ( ! ( $dbh = zmDbConnect() ) ) { +while (!$zm_terminate) { + while (!($dbh and $dbh->ping())) { + if (!($dbh = zmDbConnect())) { sleep($Config{ZM_WATCH_CHECK_INTERVAL}); } } - 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() ) { + foreach my $monitor (ZoneMinder::Monitor->find($Config{ZM_SERVER_ID} ? (ServerId=>$Config{ZM_SERVER_ID}) : ())) { next if $monitor->{Function} eq 'None'; next if $monitor->{Type} eq 'WebSite'; my $now = time(); my $restart = 0; - if ( zmMemVerify($monitor) ) { -# Check we have got an image recently - my $capture_time = zmGetLastWriteTime($monitor); - 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 ) { - my $startup_time = zmGetStartupTime($monitor); - 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}" - ); - $restart = 1; - } else { - # We can't get the last capture time so can't be sure it's died, it might just be starting up. - zmMemInvalidate($monitor); - next; - } - } - if ( ! $restart ) { - my $max_image_delay = ( - $monitor->{MaxFPS} - &&($monitor->{MaxFPS}>0) - &&($monitor->{MaxFPS}<1) - ) ? (3/$monitor->{MaxFPS}) - : $Config{ZM_WATCH_MAX_DELAY} - ; - my $image_delay = $now-$capture_time; - Debug("Monitor $monitor->{Id} last captured $image_delay seconds ago, max is $max_image_delay"); - if ( $image_delay > $max_image_delay ) { - Warning("Restarting capture daemon for " - .$monitor->{Name}.", time since last capture $image_delay seconds ($now-$capture_time)" - ); - $restart = 1; - } - } # end if ! restart - } else { + + zmMemInvalidate($monitor); + if (!zmMemVerify($monitor)) { Info("Restarting capture daemon for $monitor->{Name}, shared data not valid"); - $restart = 1; + $monitor->control('restart'); + next; } - if ( $restart ) { - # Because zma depends on zmc, and zma can hold the shm in place, preventing zmc from using the space in /dev/shm, - # we need to stop zma before restarting zmc. - runCommand("zmdc.pl stop zma -m $$monitor{Id}") if $monitor->{Function} ne 'Monitor'; - my $command; - if ( $monitor->{Type} eq 'Local' ) { - $command = "zmdc.pl restart zmc -d $monitor->{Device}"; - } else { - $command = "zmdc.pl restart zmc -m $monitor->{Id}"; + # Check we have got an image recently + my $capture_time = zmGetLastWriteTime($monitor); + if (!defined($capture_time)) { + # Can't read from shared data + Warning('LastWriteTime is not defined.'); + next; + } + Debug("Monitor $$monitor{Id} LastWriteTime is $capture_time."); + if (!$capture_time) { + # We can't get the last capture time so can't be sure it's died, it might just be starting up. + my $startup_time = zmGetStartupTime($monitor); + if (($now - $startup_time) > $Config{ZM_WATCH_MAX_DELAY}) { + $log->logPrint(ZoneMinder::Logger::WARNING+$monitor->Importance(), + "Restarting capture daemon for $$monitor{Name}, no image since startup. ". + "Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}" + ); + $monitor->control('restart'); } - runCommand($command); - runCommand("zmdc.pl start zma -m $$monitor{Id}") if $monitor->{Function} ne 'Monitor'; - } elsif ( $monitor->{Function} ne 'Monitor' ) { -# Now check analysis daemon - $restart = 0; + next; + } + + my $max_image_delay = ( + $monitor->{MaxFPS} + &&($monitor->{MaxFPS}>0) + &&($monitor->{MaxFPS}<1) + ) ? (3/$monitor->{MaxFPS}) + : $Config{ZM_WATCH_MAX_DELAY} + ; + my $image_delay = $now - $capture_time; + Debug("Monitor $monitor->{Id} last captured $image_delay seconds ago, max is $max_image_delay"); + if ($image_delay > $max_image_delay) { + $log->logPrint(ZoneMinder::Logger::WARNING+$monitor->Importance(), + 'Restarting capture daemon for '.$monitor->{Name}. + ", time since last capture $image_delay seconds ($now-$capture_time)"); + $monitor->control('restart'); + next; + } + + if ($monitor->{Function} ne 'Monitor') { + # Now check analysis thread # Check we have got an image recently my $image_time = zmGetLastReadTime($monitor); - if ( !defined($image_time) ) { -# Can't read from shared data - $restart = 1; + if (!defined($image_time)) { + # Can't read from shared data Error("Error reading shared data for $$monitor{Id} $$monitor{Name}"); - } elsif ( !$image_time ) { -# We can't get the last capture time so can't be sure it's died. - $restart = 1; - Error("Last analyse time for $$monitor{Id} $$monitor{Name} was zero."); + $monitor->control('restart'); + next; + } elsif (!$image_time) { + Debug("Last analyse time for $$monitor{Id} $$monitor{Name} was zero."); } else { - my $max_image_delay = ( $monitor->{MaxFPS} &&($monitor->{MaxFPS}>0) &&($monitor->{MaxFPS}<1) @@ -172,28 +160,23 @@ 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 ) { - Warning("Analysis daemon for $$monitor{Id} $$monitor{Name} needs restarting," - ." time since last analysis $image_delay seconds ($now-$image_time)" - ); - $restart = 1; + if ($image_delay > $max_image_delay) { + $log->logPrint(ZoneMinder::Logger::WARNING+$monitor->Importance(), + "daemon for $$monitor{Id} $$monitor{Name} needs restarting," + ." time since last analysis $image_delay seconds ($now-$image_time)"); + $monitor->control('restart'); + next; } } - if ( $restart ) { - Info("Restarting analysis daemon for $$monitor{Id} $$monitor{Name}"); - my $command = 'zmdc.pl restart zma -m '.$monitor->{Id}; - runCommand($command); - } # end if restart } # end if check analysis daemon - # Prevent open handles building up if we have connect to shared memory - zmMemInvalidate($monitor); # Close our file handle to the zmc process we are about to end + } # 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/scripts/zmx10.pl.in b/scripts/zmx10.pl.in index 89dee5d00..de1f74929 100644 --- a/scripts/zmx10.pl.in +++ b/scripts/zmx10.pl.in @@ -83,72 +83,64 @@ my $unit_code; my $version; GetOptions( - 'command=s' =>\$command, - 'unit-code=i' =>\$unit_code, - 'version' =>\$version + 'command=s' =>\$command, + 'unit-code=i' =>\$unit_code, + 'version' =>\$version ) or pod2usage(-exitstatus => -1); if ( $version ) { - print ZoneMinder::Base::ZM_VERSION; - exit(0); + print ZoneMinder::Base::ZM_VERSION; + exit(0); } -die( 'No command given' ) unless( $command ); -die( 'No unit code given' ) - unless( $unit_code || ($command =~ /(?:start|status|shutdown)/) ); +die 'No command given' unless $command; +die 'No unit code given' +unless( $unit_code || ($command =~ /(?:start|status|shutdown)/) ); -if ( $command eq 'start' ) -{ +if ( $command eq 'start' ) { + X10Server::runServer(); + exit(); +} + +socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) + or Fatal("Can't open socket: $!"); + +my $saddr = sockaddr_un(SOCK_FILE); + +if ( !connect(CLIENT, $saddr) ) { + # The server isn't there + print("Unable to connect, starting server\n"); + close(CLIENT); + + if ( my $cpid = fork() ) { + # Parent process just sleep and fall through + sleep(2); + logReinit(); + socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) + or Fatal("Can't open socket: $!"); + connect(CLIENT, $saddr) + or Fatal("Can't connect: $!"); + } elsif ( defined($cpid) ) { + setpgrp(); + + logReinit(); X10Server::runServer(); - exit(); -} - -socket( CLIENT, PF_UNIX, SOCK_STREAM, 0 ) - or Fatal( "Can't open socket: $!" ); - -my $saddr = sockaddr_un( SOCK_FILE ); - -if ( !connect( CLIENT, $saddr ) ) -{ - # The server isn't there - print( "Unable to connect, starting server\n" ); - close( CLIENT ); - - if ( my $cpid = fork() ) - { - # Parent process just sleep and fall through - sleep( 2 ); - logReinit(); - socket( CLIENT, PF_UNIX, SOCK_STREAM, 0 ) - or Fatal( "Can't open socket: $!" ); - connect( CLIENT, $saddr ) - or Fatal( "Can't connect: $!" ); - } - elsif ( defined($cpid) ) - { - setpgrp(); - - logReinit(); - X10Server::runServer(); - } - else - { - Fatal( "Can't fork: $!" ); - } + } else { + Fatal("Can't fork: $!"); + } } # The server is there, connect to it #print( "Writing commands\n" ); CLIENT->autoflush(); -my $message = "$command"; -$message .= ";$unit_code" if ( $unit_code ); -print( CLIENT $message ); -shutdown( CLIENT, 1 ); -while ( my $line = ) -{ - chomp( $line ); - print( "$line\n" ); +my $message = $command; +$message .= ';'.$unit_code if $unit_code; +print(CLIENT $message); +shutdown(CLIENT, 1); +while ( my $line = ) { + chomp($line); + print("$line\n"); } -close( CLIENT ); +close(CLIENT); #print( "Finished writing, bye\n" ); exit; @@ -178,605 +170,497 @@ our %monitor_hash; our %device_hash; our %pending_tasks; -sub runServer -{ - Info( "X10 server starting\n" ); +sub runServer { + Info('X10 server starting'); - socket( SERVER, PF_UNIX, SOCK_STREAM, 0 ) - or Fatal( "Can't open socket: $!" ); - unlink( main::SOCK_FILE ); - my $saddr = sockaddr_un( main::SOCK_FILE ); - bind( SERVER, $saddr ) or Fatal( "Can't bind: $!" ); - listen( SERVER, SOMAXCONN ) or Fatal( "Can't listen: $!" ); + socket(SERVER, PF_UNIX, SOCK_STREAM, 0) + or Fatal("Can't open socket: $!"); + unlink(main::SOCK_FILE); + my $saddr = sockaddr_un(main::SOCK_FILE); + bind(SERVER, $saddr) or Fatal("Can't bind: $!"); + listen(SERVER, SOMAXCONN) or Fatal("Can't listen: $!"); - $dbh = zmDbConnect(); + $dbh = zmDbConnect(); - $x10 = new X10::ActiveHome( port=>$Config{ZM_X10_DEVICE}, house_code=>$Config{ZM_X10_HOUSE_CODE}, debug=>0 ); + $x10 = new X10::ActiveHome( + port=>$Config{ZM_X10_DEVICE}, + house_code=>$Config{ZM_X10_HOUSE_CODE}, + debug=>0 + ); - loadTasks(); + loadTasks(); - $x10->register_listener( \&x10listen ); + $x10->register_listener(\&x10listen); - my $rin = ''; - vec( $rin, fileno(SERVER),1) = 1; - vec( $rin, $x10->select_fds(),1) = 1; - my $timeout = 0.2; - #print( 'F:'.fileno(SERVER)."\n" ); - my $reload = undef; - my $reload_count = 0; - my $reload_limit = $Config{ZM_X10_DB_RELOAD_INTERVAL} / $timeout; - while( 1 ) - { - my $nfound = select( my $rout = $rin, undef, undef, $timeout ); - #print( "Off select, NF:$nfound, ER:$!\n" ); - #print( vec( $rout, fileno(SERVER),1)."\n" ); - #print( vec( $rout, $x10->select_fds(),1)."\n" ); - if ( $nfound > 0 ) - { - if ( vec( $rout, fileno(SERVER),1) ) - { - my $paddr = accept( CLIENT, SERVER ); - my $message = ; + my $rin = ''; + vec($rin, fileno(SERVER),1) = 1; + vec($rin, $x10->select_fds(),1) = 1; + my $timeout = 0.2; + #print( 'F:'.fileno(SERVER)."\n" ); + my $reload = undef; + my $reload_count = 0; + my $reload_limit = $Config{ZM_X10_DB_RELOAD_INTERVAL} / $timeout; + while( 1 ) { + my $nfound = select(my $rout = $rin, undef, undef, $timeout); + #print( "Off select, NF:$nfound, ER:$!\n" ); + #print( vec( $rout, fileno(SERVER),1)."\n" ); + #print( vec( $rout, $x10->select_fds(),1)."\n" ); + if ( $nfound > 0 ) { + if ( vec($rout, fileno(SERVER),1) ) { + my $paddr = accept(CLIENT, SERVER); + my $message = ; - my ( $command, $unit_code ) = split( /;/, $message ); + my ($command, $unit_code) = split(';', $message); - my $device; - if ( defined($unit_code) ) - { - if ( $unit_code < 1 || $unit_code > 16 ) - { - dPrint( ZoneMinder::Logger::ERROR, "Invalid unit code '$unit_code'\n" ); - next; - } + my $device; + if ( defined($unit_code) ) { + if ( $unit_code < 1 || $unit_code > 16 ) { + dPrint(ZoneMinder::Logger::ERROR, "Invalid unit code '$unit_code'\n"); + next; + } - $device = $device_hash{$unit_code}; - if ( !$device ) - { - $device = $device_hash{$unit_code} = { appliance=>$x10->Appliance( unit_code=>$unit_code ), - status=>'unknown' - }; - } - } + $device = $device_hash{$unit_code}; + if ( !$device ) { + $device = $device_hash{$unit_code} = { + appliance=>$x10->Appliance(unit_code=>$unit_code), + status=>'unknown' + }; + } + } # end if defined($unit_code) - my $result; - if ( $command eq 'on' ) - { - $result = $device->{appliance}->on(); - } - elsif ( $command eq 'off' ) - { - $result = $device->{appliance}->off(); - } - #elsif ( $command eq 'dim' ) - #{ - #$result = $device->{appliance}->dim(); - #} - #elsif ( $command eq 'bright' ) - #{ - #$result = $device->{appliance}->bright(); - #} - elsif ( $command eq 'status' ) - { - if ( $device ) - { - dPrint( ZoneMinder::Logger::DEBUG, $unit_code.' '.$device->{status}."\n" ); - } - else - { - foreach my $unit_code ( sort( keys(%device_hash) ) ) - { - my $device = $device_hash{$unit_code}; - dPrint( ZoneMinder::Logger::DEBUG, $unit_code.' '.$device->{status}."\n" ); - } - } - } - elsif ( $command eq 'shutdown' ) - { - last; - } - else - { - dPrint( ZoneMinder::Logger::ERROR, "Invalid command '$command'\n" ); - } - if ( defined($result) ) - { - if ( 1 || $result ) - { - $device->{status} = uc($command); - dPrint( ZoneMinder::Logger::DEBUG, $device->{appliance}->address()." $command, ok\n" ); - #x10listen( new X10::Event( sprintf("%s %s", $device->{appliance}->address, uc($command) ) ) ); - } - else - { - dPrint( ZoneMinder::Logger::ERROR, $device->{appliance}->address()." $command, failed\n" ); - } - } - close( CLIENT ); - } - elsif ( vec( $rout, $x10->select_fds(),1) ) - { - $x10->handle_input(); - } - else - { - Fatal( 'Bogus descriptor' ); - } + my $result; + if ( $command eq 'on' ) { + $result = $device->{appliance}->on(); + } elsif ( $command eq 'off' ) { + $result = $device->{appliance}->off(); } - elsif ( $nfound < 0 ) - { - if ( $! != EINTR ) - { - Fatal( "Can't select: $!" ); + #elsif ( $command eq 'dim' ) + #{ + #$result = $device->{appliance}->dim(); + #} + #elsif ( $command eq 'bright' ) + #{ + #$result = $device->{appliance}->bright(); + #} + elsif ( $command eq 'status' ) { + if ( $device ) { + dPrint(ZoneMinder::Logger::DEBUG, $unit_code.' '.$device->{status}."\n"); + } else { + foreach my $unit_code ( sort( keys(%device_hash) ) ) { + my $device = $device_hash{$unit_code}; + dPrint(ZoneMinder::Logger::DEBUG, $unit_code.' '.$device->{status}."\n"); } + } + } elsif ( $command eq 'shutdown' ) { + last; + } else { + dPrint(ZoneMinder::Logger::ERROR, "Invalid command '$command'\n"); } - else - { - #print( "Select timed out\n" ); - # Check for state changes - foreach my $monitor_id ( sort(keys(%monitor_hash) ) ) - { - my $monitor = $monitor_hash{$monitor_id}; - my $state = zmGetMonitorState( $monitor ); - if ( !defined($state) ) - { - $reload = !undef; - next; - } - if ( defined( $monitor->{LastState} ) ) - { - my $task_list; - if ( ($state == STATE_ALARM || $state == STATE_ALERT) - && ($monitor->{LastState} == STATE_IDLE || $monitor->{LastState} == STATE_TAPE) - ) # Gone into alarm state - { - Debug( "Applying ON_list for $monitor_id\n" ); - $task_list = $monitor->{'ON_list'}; - } - elsif ( ($state == STATE_IDLE && $monitor->{LastState} != STATE_IDLE) - || ($state == STATE_TAPE && $monitor->{LastState} != STATE_TAPE) - ) # Come out of alarm state - { - Debug( "Applying OFF_list for $monitor_id\n" ); - $task_list = $monitor->{'OFF_list'}; - } - if ( $task_list ) - { - foreach my $task ( @$task_list ) - { - processTask( $task ); - } - } - } - $monitor->{LastState} = $state; - } - - # Check for pending tasks - my $now = time(); - foreach my $activation_time ( sort(keys(%pending_tasks) ) ) - { - last if ( $activation_time > $now ); - my $pending_list = $pending_tasks{$activation_time}; - foreach my $task ( @$pending_list ) - { - processTask( $task ); - } - delete( $pending_tasks{$activation_time} ); - } - if ( $reload || ++$reload_count >= $reload_limit ) - { - loadTasks(); - $reload = undef; - $reload_count = 0; - } - } - } - Info( "X10 server exiting\n" ); - close( SERVER ); - exit(); -} - -sub addToDeviceList -{ - my $unit_code = shift; - my $event = shift; - my $monitor = shift; - my $function = shift; - my $limit = shift; - - Debug( "Adding to device list, uc:$unit_code, ev:$event, mo:" - .$monitor->{Id}.", fu:$function, li:$limit\n" - ); - my $device = $device_hash{$unit_code}; - if ( !$device ) - { - $device = $device_hash{$unit_code} = { appliance=>$x10->Appliance( unit_code=>$unit_code ), - status=>'unknown' - }; - } - - my $task = { type=>'device', - monitor=>$monitor, - address=>$device->{appliance}->address(), - function=>$function - }; - if ( $limit ) - { - $task->{limit} = $limit - } - - my $task_list = $device->{$event.'_list'}; - if ( !$task_list ) - { - $task_list = $device->{$event.'_list'} = []; - } - push( @$task_list, $task ); -} - -sub addToMonitorList -{ - my $monitor = shift; - my $event = shift; - my $unit_code = shift; - my $function = shift; - my $limit = shift; - - Debug( "Adding to monitor list, uc:$unit_code, ev:$event, mo:".$monitor->{Id} - .", fu:$function, li:$limit\n" - ); - my $device = $device_hash{$unit_code}; - if ( !$device ) - { - $device = $device_hash{$unit_code} = { appliance=>$x10->Appliance( unit_code=>$unit_code ), - status=>'unknown' - }; - } - - my $task = { type=>'monitor', - device=>$device, - id=>$monitor->{Id}, - function=>$function - }; - if ( $limit ) - { - $task->{limit} = $limit; - } - - my $task_list = $monitor->{$event.'_list'}; - if ( !$task_list ) - { - $task_list = $monitor->{$event.'_list'} = []; - } - push( @$task_list, $task ); -} - -sub loadTasks -{ - %monitor_hash = (); - - Debug( "Loading tasks\n" ); - # Clear out all old device task lists - foreach my $unit_code ( sort( keys(%device_hash) ) ) - { - my $device = $device_hash{$unit_code}; - $device->{ON_list} = []; - $device->{OFF_list} = []; - } - - my $sql = "SELECT M.*,T.* from Monitors as M - INNER JOIN TriggersX10 as T on (M.Id = T.MonitorId) - WHERE find_in_set( M.Function, 'Modect,Record,Mocord,Nodect' ) - AND M.Enabled = 1 - AND find_IN_set( 'X10', M.Triggers )" - ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() - or Fatal( "Can't execute: ".$sth->errstr() ); - while( my $monitor = $sth->fetchrow_hashref() ) - { -# Check shared memory ok - if ( !zmMemVerify( $monitor ) ) { - zmMemInvalidate( $monitor ); - next ; + if ( defined($result) ) { + # FIXME + if ( 1 || $result ) { + $device->{status} = uc($command); + dPrint(ZoneMinder::Logger::DEBUG, $device->{appliance}->address()." $command, ok\n"); + #x10listen( new X10::Event( sprintf("%s %s", $device->{appliance}->address, uc($command) ) ) ); + } else { + dPrint(ZoneMinder::Logger::ERROR, $device->{appliance}->address()." $command, failed\n"); + } + } # end if defined result + close(CLIENT); + } elsif ( vec($rout, $x10->select_fds(),1) ) { + $x10->handle_input(); + } else { + Fatal('Bogus descriptor'); } - - $monitor_hash{$monitor->{Id}} = $monitor; - - if ( $monitor->{Activation} ) - { - Debug( "$monitor->{Name} has active string '$monitor->{Activation}'\n" ); - foreach my $code_string ( split( /,/, $monitor->{Activation} ) ) - { - #Debug( "Code string: $code_string\n" ); - my ( $invert, $unit_code, $modifier, $limit ) - = ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ ); - $limit = 0 if ( !$limit ); - if ( $unit_code ) - { - if ( !$modifier || $modifier eq '+' ) - { - addToDeviceList( $unit_code, - 'ON', - $monitor, - !$invert ? 'start_active' - : 'stop_active', - $limit - ); - } - if ( !$modifier || $modifier eq '-' ) - { - addToDeviceList( $unit_code, - 'OFF', - $monitor, - !$invert ? 'stop_active' - : 'start_active', - $limit - ); - } - } - } + } elsif ( $nfound < 0 ) { + if ( $! != EINTR ) { + Fatal("Can't select: $!"); + } + } else { + #print( "Select timed out\n" ); + # Check for state changes + foreach my $monitor_id ( sort(keys(%monitor_hash) ) ) { + my $monitor = $monitor_hash{$monitor_id}; + my $state = zmGetMonitorState($monitor); + if ( !defined($state) ) { + $reload = !undef; + next; } - if ( $monitor->{AlarmInput} ) - { - Debug( "$monitor->{Name} has alarm input string '$monitor->{AlarmInput}'\n" ); - foreach my $code_string ( split( /,/, $monitor->{AlarmInput} ) ) - { - #Debug( "Code string: $code_string\n" ); - my ( $invert, $unit_code, $modifier, $limit ) - = ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ ); - $limit = 0 if ( !$limit ); - if ( $unit_code ) - { - if ( !$modifier || $modifier eq '+' ) - { - addToDeviceList( $unit_code, - 'ON', - $monitor, - !$invert ? 'start_alarm' - : 'stop_alarm', - $limit - ); - } - if ( !$modifier || $modifier eq '-' ) - { - addToDeviceList( $unit_code, - 'OFF', - $monitor, - !$invert ? 'stop_alarm' - : 'start_alarm', - $limit - ); - } - } + if ( defined( $monitor->{LastState} ) ) { + my $task_list; + if ( ($state == STATE_ALARM || $state == STATE_ALERT) + && ($monitor->{LastState} == STATE_IDLE || $monitor->{LastState} == STATE_TAPE) + ) # Gone into alarm state + { + Debug("Applying ON_list for $monitor_id"); + $task_list = $monitor->{ON_list}; + } elsif ( ($state == STATE_IDLE && $monitor->{LastState} != STATE_IDLE) + || ($state == STATE_TAPE && $monitor->{LastState} != STATE_TAPE) + ) # Come out of alarm state + { + Debug("Applying OFF_list for $monitor_id"); + $task_list = $monitor->{OFF_list}; + } + if ( $task_list ) { + foreach my $task ( @$task_list ) { + processTask($task); } - } - if ( $monitor->{AlarmOutput} ) - { - Debug( "$monitor->{Name} has alarm output string '$monitor->{AlarmOutput}'\n" ); - foreach my $code_string ( split( /,/, $monitor->{AlarmOutput} ) ) - { - #Debug( "Code string: $code_string\n" ); - my ( $invert, $unit_code, $modifier, $limit ) - = ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ ); - $limit = 0 if ( !$limit ); - if ( $unit_code ) - { - if ( !$modifier || $modifier eq '+' ) - { - addToMonitorList( $monitor, - 'ON', - $unit_code, - !$invert ? 'on' - : 'off', - $limit - ); - } - if ( !$modifier || $modifier eq '-' ) - { - addToMonitorList( $monitor, - 'OFF', - $unit_code, - !$invert ? 'off' - : 'on', - $limit - ); - } - } - } - } - zmMemInvalidate( $monitor ); - } -} + } + } # end if defined laststate + $monitor->{LastState} = $state; + } # end foreach monitor -sub addPendingTask -{ - my $task = shift; - - # Check whether we are just extending a previous pending task - # and remove it if it's there - foreach my $activation_time ( sort(keys(%pending_tasks) ) ) - { + # Check for pending tasks + my $now = time(); + foreach my $activation_time ( sort(keys(%pending_tasks) ) ) { + last if ( $activation_time > $now ); my $pending_list = $pending_tasks{$activation_time}; - my $new_pending_list = []; - foreach my $pending_task ( @$pending_list ) - { - if ( $task->{type} ne $pending_task->{type} ) - { - push( @$new_pending_list, $pending_task ) - } - elsif ( $task->{type} eq 'device' ) - { - if (( $task->{monitor}->{Id} != $pending_task->{monitor}->{Id} ) - || ( $task->{function} ne $pending_task->{function} )) - { - push( @$new_pending_list, $pending_task ) - } - } - elsif ( $task->{type} eq 'monitor' ) - { - if (( $task->{device}->{appliance}->unit_code() - != $pending_task->{device}->{appliance}->unit_code() - ) - || ( $task->{function} ne $pending_task->{function} ) - ) - { - push( @$new_pending_list, $pending_task ) - } - } - } - if ( @$new_pending_list ) - { - $pending_tasks{$activation_time} = $new_pending_list; - } - else - { - delete( $pending_tasks{$activation_time} ); + foreach my $task ( @$pending_list ) { + processTask($task); } + delete $pending_tasks{$activation_time}; + } + if ( $reload or (++$reload_count >= $reload_limit) ) { + loadTasks(); + $reload = undef; + $reload_count = 0; + } + } + } + Info("X10 server exiting"); + close(SERVER); + exit(); +} + +sub addToDeviceList { + my $unit_code = shift; + my $event = shift; + my $monitor = shift; + my $function = shift; + my $limit = shift; + + Debug("Adding to device list, uc:$unit_code, ev:$event, mo:" + .$monitor->{Id}.", fu:$function, li:$limit" + ); + my $device = $device_hash{$unit_code}; + if ( !$device ) { + $device = $device_hash{$unit_code} = { + appliance=>$x10->Appliance(unit_code=>$unit_code), + status=>'unknown' + }; + } + + my $task = { + type=>'device', + monitor=>$monitor, + address=>$device->{appliance}->address(), + function=>$function + }; + + if ( $limit ) { + $task->{limit} = $limit + } + + my $task_list = $device->{$event.'_list'}; + if ( !$task_list ) { + $task_list = $device->{$event.'_list'} = []; + } + push @$task_list, $task; +} # end sub addToDeviceList + +sub addToMonitorList { + my $monitor = shift; + my $event = shift; + my $unit_code = shift; + my $function = shift; + my $limit = shift; + + Debug("Adding to monitor list, uc:$unit_code, ev:$event, mo:".$monitor->{Id} + .", fu:$function, li:$limit" + ); + my $device = $device_hash{$unit_code}; + if ( !$device ) { + $device = $device_hash{$unit_code} = { + appliance=>$x10->Appliance(unit_code=>$unit_code), + status=>'unknown' + }; + } + + my $task = { + type=>'monitor', + device=>$device, + id=>$monitor->{Id}, + function=>$function + }; + if ( $limit ) { + $task->{limit} = $limit; + } + + my $task_list = $monitor->{$event.'_list'}; + if ( !$task_list ) { + $task_list = $monitor->{$event.'_list'} = []; + } + push @$task_list, $task; +} # end sub addToMonitorList + +sub loadTasks { + %monitor_hash = (); + + Debug('Loading tasks'); + # Clear out all old device task lists + foreach my $unit_code ( sort keys(%device_hash) ) { + my $device = $device_hash{$unit_code}; + $device->{ON_list} = []; + $device->{OFF_list} = []; + } + + my $sql = 'SELECT M.*,T.* FROM Monitors as M + INNER JOIN TriggersX10 as T on (M.Id = T.MonitorId) + WHERE find_in_set(M.`Function`, \'Modect,Record,Mocord,Nodect\') + AND M.`Enabled` = 1 + AND find_IN_set(\'X10\', M.Triggers)'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal("Can't prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute() + or Fatal("Can't execute: ".$sth->errstr()); + while( my $monitor = $sth->fetchrow_hashref() ) { + # Check shared memory ok + if ( !zmMemVerify($monitor) ) { + zmMemInvalidate($monitor); + next; } - my $end_time = time() + $task->{limit}; - my $pending_list = $pending_tasks{$end_time}; - if ( !$pending_list ) - { - $pending_list = $pending_tasks{$end_time} = []; + $monitor_hash{$monitor->{Id}} = $monitor; + + if ( $monitor->{Activation} ) { + Debug("$monitor->{Name} has active string '$monitor->{Activation}'"); + foreach my $code_string ( split(',', $monitor->{Activation}) ) { + #Debug( "Code string: $code_string\n" ); + my ( $invert, $unit_code, $modifier, $limit ) + = ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ ); + $limit = 0 if !$limit; + if ( $unit_code ) { + if ( !$modifier || $modifier eq '+' ) { + addToDeviceList( $unit_code, + 'ON', + $monitor, + (!$invert ? 'start_active' : 'stop_active'), + $limit + ); + } + if ( !$modifier || $modifier eq '-' ) { + addToDeviceList( $unit_code, + 'OFF', + $monitor, + (!$invert ? 'stop_active' : 'start_active'), + $limit + ); + } + } # end if unit_code + } # end foreach code_string } - my $pending_task; - if ( $task->{type} eq 'device' ) - { - $pending_task = { type=>$task->{type}, - monitor=>$task->{monitor}, - function=>$task->{function} + if ( $monitor->{AlarmInput} ) { + Debug("$monitor->{Name} has alarm input string '$monitor->{AlarmInput}'"); + foreach my $code_string ( split(',', $monitor->{AlarmInput}) ) { + #Debug( "Code string: $code_string\n" ); + my ( $invert, $unit_code, $modifier, $limit ) + = ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ ); + $limit = 0 if !$limit; + if ( $unit_code ) { + if ( !$modifier || $modifier eq '+' ) { + addToDeviceList( $unit_code, + 'ON', + $monitor, + (!$invert ? 'start_alarm' : 'stop_alarm'), + $limit + ); + } + if ( !$modifier || $modifier eq '-' ) { + addToDeviceList( $unit_code, + 'OFF', + $monitor, + (!$invert ? 'stop_alarm' : 'start_alarm'), + $limit + ); + } + } # end if unit_code + } # end foreach code_string + } # end if AlarmInput + if ( $monitor->{AlarmOutput} ) { + Debug("$monitor->{Name} has alarm output string '$monitor->{AlarmOutput}'"); + foreach my $code_string ( split( ',', $monitor->{AlarmOutput} ) ) { + #Debug( "Code string: $code_string\n" ); + my ( $invert, $unit_code, $modifier, $limit ) + = ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ ); + $limit = 0 if !$limit; + if ( $unit_code ) { + if ( !$modifier || $modifier eq '+' ) { + addToMonitorList( $monitor, + 'ON', + $unit_code, + (!$invert ? 'on' : 'off'), + $limit + ); + } + if ( !$modifier || $modifier eq '-' ) { + addToMonitorList( $monitor, + 'OFF', + $unit_code, + (!$invert ? 'off' : 'on'), + $limit + ); + } + } # end if unit_code + } # end foreach code_string + } # end if AlarmOutput + zmMemInvalidate($monitor); + } +} # end sub loadTasks + +sub addPendingTask { + my $task = shift; + + # Check whether we are just extending a previous pending task + # and remove it if it's there + foreach my $activation_time ( sort keys(%pending_tasks) ) { + my $pending_list = $pending_tasks{$activation_time}; + my $new_pending_list = []; + foreach my $pending_task ( @$pending_list ) { + if ( $task->{type} ne $pending_task->{type} ) { + push( @$new_pending_list, $pending_task ) + } elsif ( $task->{type} eq 'device' ) { + if (( $task->{monitor}->{Id} != $pending_task->{monitor}->{Id} ) + || ( $task->{function} ne $pending_task->{function} )) + { + push @$new_pending_list, $pending_task; + } + } elsif ( $task->{type} eq 'monitor' ) { + if (( $task->{device}->{appliance}->unit_code() + != $pending_task->{device}->{appliance}->unit_code() + ) + || ( $task->{function} ne $pending_task->{function} ) + ) { + push @$new_pending_list, $pending_task; + } + } # end switch task->type + } # end foreach pending_task + + if ( @$new_pending_list ) { + $pending_tasks{$activation_time} = $new_pending_list; + } else { + delete $pending_tasks{$activation_time}; + } + } # end foreach activation_time + + my $end_time = time() + $task->{limit}; + my $pending_list = $pending_tasks{$end_time}; + if ( !$pending_list ) { + $pending_list = $pending_tasks{$end_time} = []; + } + my $pending_task; + if ( $task->{type} eq 'device' ) { + $pending_task = { + type=>$task->{type}, + monitor=>$task->{monitor}, + function=>$task->{function} + }; + $pending_task->{function} =~ s/start/stop/; + } elsif ( $task->{type} eq 'monitor' ) { + $pending_task = { + type=>$task->{type}, + device=>$task->{device}, + function=>$task->{function} + }; + $pending_task->{function} =~ s/on/off/; + } + push @$pending_list, $pending_task; +} # end sub addPendingTask + +sub processTask { + my $task = shift; + + if ( $task->{type} eq 'device' ) { + my ( $instruction, $class ) = ( $task->{function} =~ /^(.+)_(.+)$/ ); + + if ( $class eq 'active' ) { + if ( $instruction eq 'start' ) { + zmMonitorEnable($task->{monitor}); + if ( $task->{limit} ) { + addPendingTask($task); + } + } elsif( $instruction eq 'stop' ) { + zmMonitorDisable($task->{monitor}); + } + } elsif( $class eq 'alarm' ) { + if ( $instruction eq 'start' ) { + zmTriggerEventOn( + $task->{monitor}, + 0, + main::CAUSE_STRING, + $task->{address} + ); + if ( $task->{limit} ) { + addPendingTask($task); + } + } elsif( $instruction eq 'stop' ) { + zmTriggerEventCancel($task->{monitor}); + } + } # end switch class + } elsif( $task->{type} eq 'monitor' ) { + if ( $task->{function} eq 'on' ) { + $task->{device}->{appliance}->on(); + if ( $task->{limit} ) { + addPendingTask($task); + } + } elsif ( $task->{function} eq 'off' ) { + $task->{device}->{appliance}->off(); + } + } +} + +sub dPrint { + my $dbg_level = shift; + if ( fileno(CLIENT) ) { + print CLIENT @_ + } + if ( $dbg_level == ZoneMinder::Logger::DEBUG ) { + Debug(@_); + } elsif ( $dbg_level == ZoneMinder::Logger::INFO ) { + Info(@_); + } elsif ( $dbg_level == ZoneMinder::Logger::WARNING ) { + Warning(@_); + } + elsif ( $dbg_level == ZoneMinder::Logger::ERROR ) { + Error( @_ ); + } elsif ( $dbg_level == ZoneMinder::Logger::FATAL ) { + Fatal( @_ ); + } +} + +sub x10listen { + foreach my $event ( @_ ) { + #print( Data::Dumper( $_ )."\n" ); + if ( $event->house_code() eq $Config{ZM_X10_HOUSE_CODE} ) { + my $unit_code = $event->unit_code(); + my $device = $device_hash{$unit_code}; + if ( !$device ) { + $device = $device_hash{$unit_code} = { + appliance=>$x10->Appliance(unit_code=>$unit_code), + status=>'unknown' }; - $pending_task->{function} =~ s/start/stop/; - } - elsif ( $task->{type} eq 'monitor' ) - { - $pending_task = { type=>$task->{type}, - device=>$task->{device}, - function=>$task->{function} - }; - $pending_task->{function} =~ s/on/off/; - } - push( @$pending_list, $pending_task ); -} - -sub processTask -{ - my $task = shift; - - if ( $task->{type} eq 'device' ) - { - my ( $instruction, $class ) = ( $task->{function} =~ /^(.+)_(.+)$/ ); - - if ( $class eq 'active' ) - { - if ( $instruction eq 'start' ) - { - zmMonitorEnable( $task->{monitor} ); - if ( $task->{limit} ) - { - addPendingTask( $task ); - } - } - elsif( $instruction eq 'stop' ) - { - zmMonitorDisable( $task->{monitor} ); - } + } + next if ( $event->func() !~ /(?:ON|OFF)/ ); + $device->{status} = $event->func(); + my $task_list = $device->{$event->func().'_list'}; + if ( $task_list ) { + foreach my $task ( @$task_list ) { + processTask($task); } - elsif( $class eq 'alarm' ) - { - if ( $instruction eq 'start' ) - { - zmTriggerEventOn( $task->{monitor}, - 0, - main::CAUSE_STRING, - $task->{address} - ); - if ( $task->{limit} ) - { - addPendingTask( $task ); - } - } - elsif( $instruction eq 'stop' ) - { - zmTriggerEventCancel( $task->{monitor} ); - } - } - } - elsif( $task->{type} eq 'monitor' ) - { - if ( $task->{function} eq 'on' ) - { - $task->{device}->{appliance}->on(); - if ( $task->{limit} ) - { - addPendingTask( $task ); - } - } - elsif ( $task->{function} eq 'off' ) - { - $task->{device}->{appliance}->off(); - } - } -} - -sub dPrint -{ - my $dbg_level = shift; - if ( fileno(CLIENT) ) - { - print CLIENT @_ - } - if ( $dbg_level == ZoneMinder::Logger::DEBUG ) - { - Debug( @_ ); - } - elsif ( $dbg_level == ZoneMinder::Logger::INFO ) - { - Info( @_ ); - } - elsif ( $dbg_level == ZoneMinder::Logger::WARNING ) - { - Warning( @_ ); - } - elsif ( $dbg_level == ZoneMinder::Logger::ERROR ) - { - Error( @_ ); - } - elsif ( $dbg_level == ZoneMinder::Logger::FATAL ) - { - Fatal( @_ ); - } -} - -sub x10listen -{ - foreach my $event ( @_ ) - { - #print( Data::Dumper( $_ )."\n" ); - if ( $event->house_code() eq $Config{ZM_X10_HOUSE_CODE} ) - { - my $unit_code = $event->unit_code(); - my $device = $device_hash{$unit_code}; - if ( !$device ) - { - $device = $device_hash{$unit_code} = { appliance=>$x10->Appliance( unit_code=>$unit_code ), - status=>'unknown' - }; - } - next if ( $event->func() !~ /(?:ON|OFF)/ ); - $device->{status} = $event->func(); - my $task_list = $device->{$event->func().'_list'}; - if ( $task_list ) - { - foreach my $task ( @$task_list ) - { - processTask( $task ); - } - } - } - Info( "Got event - ".$event->as_string()."\n" ); - } -} + } + } # end if correct house code + Info('Got event - '.$event->as_string()); + } +} # end sub x10listen 1; +__END__ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 331f9e039..6943a102a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,37 +1,240 @@ # CMakeLists.txt for the ZoneMinder binaries # Create files from the .in files -configure_file(zm_config.h.in "${CMAKE_CURRENT_BINARY_DIR}/zm_config.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, zma, zmu, zms etc) -set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_frame.cpp zm_eventstream.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_input.cpp zm_ffmpeg_camera.cpp zm_group.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_monitorstream.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_packet.cpp zm_packetqueue.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_nvsocket.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_swscale.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_video.cpp zm_videostore.cpp zm_zone.cpp zm_storage.cpp zm_fifo.cpp zm_crypt.cpp) +# 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_poll_thread.cpp + zm_buffer.cpp + zm_camera.cpp + zm_comms.cpp + zm_config.cpp + zm_curl_camera.cpp + zm_crypt.cpp + zm.cpp + zm_db.cpp + zm_decoder_thread.cpp + zm_logger.cpp + zm_event.cpp + zm_eventstream.cpp + zm_exception.cpp + zm_fifo.cpp + zm_fifo_debug.cpp + zm_fifo_stream.cpp + zm_file_camera.cpp + zm_font.cpp + zm_frame.cpp + zm_group.cpp + zm_image.cpp + zm_jpeg.cpp + zm_libvlc_camera.cpp + zm_libvnc_camera.cpp + zm_local_camera.cpp + zm_monitor.cpp + zm_monitor_monitorlink.cpp + zm_monitor_janus.cpp + zm_monitor_amcrest.cpp + zm_monitorstream.cpp + zm_ffmpeg.cpp + zm_ffmpeg_camera.cpp + zm_ffmpeg_input.cpp + zm_mpeg.cpp + zm_packet.cpp + zm_packetqueue.cpp + zm_poly.cpp + zm_regexp.cpp + zm_remote_camera.cpp + zm_remote_camera_http.cpp + zm_remote_camera_nvsocket.cpp + zm_remote_camera_rtsp.cpp + zm_rtp.cpp + zm_rtp_ctrl.cpp + zm_rtp_data.cpp + zm_rtp_source.cpp + zm_rtsp.cpp + zm_rtsp_auth.cpp + zm_rtsp_server_fifo_source.cpp + zm_rtsp_server_fifo_adts_source.cpp + zm_rtsp_server_fifo_h264_source.cpp + zm_rtsp_server_fifo_audio_source.cpp + zm_rtsp_server_fifo_video_source.cpp + zm_sdp.cpp + zm_signal.cpp + zm_stream.cpp + zm_swscale.cpp + zm_time.cpp + zm_user.cpp + zm_utils.cpp + zm_videostore.cpp + zm_zone.cpp + zm_storage.cpp) +if(GSOAP_FOUND) + set(ZM_BIN_SRC_FILES + ${ZM_BIN_SRC_FILES} + ${CMAKE_BINARY_DIR}/generated/soapPullPointSubscriptionBindingProxy.cpp + ${CMAKE_BINARY_DIR}/generated/soapC.cpp + ${GSOAP_PLUGIN_DIR}/smdevp.c + ${GSOAP_PLUGIN_DIR}/mecevp.c + ${GSOAP_PLUGIN_DIR}/wsaapi.c + ${GSOAP_PLUGIN_DIR}/wsseapi.c + ${GSOAP_PLUGIN_DIR}/../custom/struct_timeval.c + ) + + SET(GCC_COMPILE_FLAGS "-DWITH_OPENSSL -DWITH_DOM") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COMPILE_FLAGS}") + + #Create the directory that will host files generated by GSOAP + file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/generated) + + #some files are generated by gsoap + set_source_files_properties( ${CMAKE_BINARY_DIR}/generated/soapClientLib.c PROPERTIES GENERATED TRUE ) + set_source_files_properties( ${CMAKE_BINARY_DIR}/generated/soapC.c PROPERTIES GENERATED TRUE ) + set_source_files_properties( ${CMAKE_BINARY_DIR}/generated/soapPullPointSubscriptionBindingProxy.cpp PROPERTIES GENERATED TRUE ) + set_source_files_properties( ${GSOAP_PLUGIN_DIR}/smdevp.c PROPERTIES LANGUAGE CXX) + set_source_files_properties( ${GSOAP_PLUGIN_DIR}/mecevp.c PROPERTIES LANGUAGE CXX) + set_source_files_properties( ${GSOAP_PLUGIN_DIR}/wsaapi.c PROPERTIES LANGUAGE CXX) + set_source_files_properties( ${GSOAP_PLUGIN_DIR}/wsseapi.c PROPERTIES LANGUAGE CXX) + set_source_files_properties( ${GSOAP_PLUGIN_DIR}/../custom/struct_timeval.c PROPERTIES LANGUAGE CXX) + + #Create a cmake target that generate gsoap files + add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/generated/soapC.cpp + OUTPUT ${CMAKE_BINARY_DIR}/generated/soapPullPointSubscriptionBindingProxy.cpp + COMMAND ${GSOAP_WSDL2H} -d -P -O2 -o ${CMAKE_BINARY_DIR}/generated/bindings.h http://www.onvif.org/onvif/ver10/events/wsdl/event.wsdl + COMMAND echo '\#import \"wsse.h\"' >> ${CMAKE_BINARY_DIR}/generated/bindings.h + COMMAND echo '\#import \"struct_timeval.h\"' >> ${CMAKE_BINARY_DIR}/generated/bindings.h + COMMAND ${GSOAP_SOAPCPP2} -n -2 -C -I ${GSOAP_PLUGIN_DIR}/.. -I ${GSOAP_PLUGIN_DIR}/../import/ -I ${GSOAP_PLUGIN_DIR}/../custom/ -d ${CMAKE_BINARY_DIR}/generated -j -x ${CMAKE_BINARY_DIR}/generated/bindings.h + COMMENT "CREATING STUBS AND GLUE CODE" + ) + + add_custom_target(GSOAP_GENERATION_TARGET + DEPENDS ${CMAKE_BINARY_DIR}/generated/soapC.cpp + DEPENDS ${CMAKE_BINARY_DIR}/generated/soapPullPointSubscriptionBindingProxy.cpp + DEPENDS ${GSOAP_PLUGIN_DIR}/smdevp.c + DEPENDS ${GSOAP_PLUGIN_DIR}/mecevp.c + DEPENDS ${GSOAP_PLUGIN_DIR}/wsaapi.c + DEPENDS ${GSOAP_PLUGIN_DIR}/wsseapi.c + DEPENDS ${GSOAP_PLUGIN_DIR}/../custom/struct_timeval.c + ) + +endif() # A fix for cmake recompiling the source files for every target. add_library(zm STATIC ${ZM_BIN_SRC_FILES}) -link_directories(libbcrypt) + +target_include_directories(zm + PUBLIC + ${CMAKE_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}) + +if(GSOAP_FOUND) +target_include_directories(zm + PUBLIC + ${CMAKE_BINARY_DIR}/generated + ${GSOAP_PLUGIN_DIR}/.. + ${GSOAP_INCLUDE_DIR}) + +endif() + +target_link_libraries(zm + PUBLIC + FFMPEG::avcodec + FFMPEG::avformat + FFMPEG::avutil + FFMPEG::swresample + FFMPEG::swscale + libbcrypt::bcrypt + RtspServer::RtspServer + martinmoene::span-lite + ${ZM_BIN_LIBS} + PRIVATE + zm-core-interface) + +if(GSOAP_FOUND) + target_link_libraries(zm + PUBLIC + ${GSOAP_CXX_LIBRARIES} + ${GSOAP_SSL_CXX_LIBRARIES} + ${OPENSSL_SSL_LIBRARY} + ${OPENSSL_CRYPTO_LIBRARY}) +endif() + +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(zma zma.cpp) -add_executable(zmu zmu.cpp) add_executable(zms zms.cpp) +add_executable(zmu zmu.cpp) +add_executable(zmbenchmark zmbenchmark.cpp) -# JWT is a header only library. -include_directories(libbcrypt/include/bcrypt) -include_directories(jwt-cpp/include/jwt-cpp) +if(GSOAP_FOUND) + #Make sure that the client is compiled only after gsoap has been processed + add_dependencies(zmc GSOAP_GENERATION_TARGET) +endif() -target_link_libraries(zmc zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS}) -target_link_libraries(zma zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS}) -target_link_libraries(zmu zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS} bcrypt) -target_link_libraries(zms zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS} bcrypt) +target_link_libraries(zmc + PRIVATE + zm-core-interface + zm + ${ZM_EXTRA_LIBS} + ${CMAKE_DL_LIBS}) + +target_link_libraries(zms + PRIVATE + zm-core-interface + zm + ${ZM_EXTRA_LIBS} + ${CMAKE_DL_LIBS}) + +target_link_libraries(zmu + PRIVATE + zm-core-interface + zm + ${ZM_EXTRA_LIBS} + ${CMAKE_DL_LIBS}) + +target_link_libraries(zmbenchmark + PRIVATE + zm-core-interface + zm + ${ZM_EXTRA_LIBS} + ${CMAKE_DL_LIBS}) # Generate man files for the binaries destined for the bin folder -FOREACH(CBINARY zma zmc zmu) - POD2MAN(${CMAKE_CURRENT_SOURCE_DIR}/${CBINARY}.cpp zoneminder-${CBINARY} 8 ${ZM_MANPAGE_DEST_PREFIX}) -ENDFOREACH(CBINARY zma zmc zmu) +if(BUILD_MAN) + foreach(CBINARY zmc zmu) + POD2MAN(${CMAKE_CURRENT_SOURCE_DIR}/${CBINARY}.cpp ${CBINARY} 8 ${ZM_MANPAGE_DEST_PREFIX}) + endforeach(CBINARY zmc zmu) +endif() -install(TARGETS zmc zma zmu RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +install(TARGETS zmc zmu RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(TARGETS zms RUNTIME DESTINATION "${ZM_CGIDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -install(CODE "execute_process(COMMAND ln -sf zms nph-zms WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})" ) +install(CODE "execute_process(COMMAND ln -sf zms nph-zms WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/nph-zms DESTINATION "${ZM_CGIDIR}") +if(HAVE_RTSP_SERVER) + add_executable(zm_rtsp_server zm_rtsp_server.cpp) + target_link_libraries(zm_rtsp_server + PRIVATE + zm-core-interface + zm + ${ZM_EXTRA_LIBS} + ${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/jwt-cpp/BaseTest.cpp b/src/jwt-cpp/BaseTest.cpp deleted file mode 100644 index aceeb38d4..000000000 --- a/src/jwt-cpp/BaseTest.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include -#include "include/jwt-cpp/base.h" - -TEST(BaseTest, Base64Decode) { - ASSERT_EQ("1", jwt::base::decode("MQ==")); - ASSERT_EQ("12", jwt::base::decode("MTI=")); - ASSERT_EQ("123", jwt::base::decode("MTIz")); - ASSERT_EQ("1234", jwt::base::decode("MTIzNA==")); -} - -TEST(BaseTest, Base64DecodeURL) { - ASSERT_EQ("1", jwt::base::decode("MQ%3d%3d")); - ASSERT_EQ("12", jwt::base::decode("MTI%3d")); - ASSERT_EQ("123", jwt::base::decode("MTIz")); - ASSERT_EQ("1234", jwt::base::decode("MTIzNA%3d%3d")); -} - -TEST(BaseTest, Base64Encode) { - ASSERT_EQ("MQ==", jwt::base::encode("1")); - ASSERT_EQ("MTI=", jwt::base::encode("12")); - ASSERT_EQ("MTIz", jwt::base::encode("123")); - ASSERT_EQ("MTIzNA==", jwt::base::encode("1234")); -} - -TEST(BaseTest, Base64EncodeURL) { - ASSERT_EQ("MQ%3d%3d", jwt::base::encode("1")); - ASSERT_EQ("MTI%3d", jwt::base::encode("12")); - ASSERT_EQ("MTIz", jwt::base::encode("123")); - ASSERT_EQ("MTIzNA%3d%3d", jwt::base::encode("1234")); -} \ No newline at end of file diff --git a/src/jwt-cpp/ClaimTest.cpp b/src/jwt-cpp/ClaimTest.cpp deleted file mode 100644 index 749edf2ef..000000000 --- a/src/jwt-cpp/ClaimTest.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include -#include "include/jwt-cpp/jwt.h" - -TEST(ClaimTest, AudienceAsString) { - std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.WZnM3SIiSRHsbO3O7Z2bmIzTJ4EC32HRBKfLznHhrh4"; - auto decoded = jwt::decode(token); - - ASSERT_TRUE(decoded.has_algorithm()); - ASSERT_TRUE(decoded.has_type()); - ASSERT_FALSE(decoded.has_content_type()); - ASSERT_FALSE(decoded.has_key_id()); - ASSERT_FALSE(decoded.has_issuer()); - ASSERT_FALSE(decoded.has_subject()); - ASSERT_TRUE(decoded.has_audience()); - ASSERT_FALSE(decoded.has_expires_at()); - ASSERT_FALSE(decoded.has_not_before()); - ASSERT_FALSE(decoded.has_issued_at()); - ASSERT_FALSE(decoded.has_id()); - - ASSERT_EQ("HS256", decoded.get_algorithm()); - ASSERT_EQ("JWT", decoded.get_type()); - auto aud = decoded.get_audience(); - ASSERT_EQ(1, aud.size()); - ASSERT_EQ("test", *aud.begin()); -} - -TEST(ClaimTest, SetAudienceAsString) { - auto token = jwt::create() - .set_type("JWT") - .set_audience("test") - .sign(jwt::algorithm::hs256("test")); - ASSERT_EQ("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.ny5Fa0vzAg7tNL95KWg_ecBNd3XP3tdAzq0SFA6diY4", token); -} diff --git a/src/jwt-cpp/HelperTest.cpp b/src/jwt-cpp/HelperTest.cpp deleted file mode 100644 index f998e1289..000000000 --- a/src/jwt-cpp/HelperTest.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include -#include "include/jwt-cpp/jwt.h" - -namespace { - extern std::string google_cert; - extern std::string google_cert_key; -} - -TEST(HelperTest, Cert2Pubkey) { - auto key = jwt::helper::extract_pubkey_from_cert(google_cert); - ASSERT_EQ(google_cert_key, key); -} - -namespace { - std::string google_cert = R"(-----BEGIN CERTIFICATE----- -MIIF8DCCBVmgAwIBAgIKYFOB9QABAACIvTANBgkqhkiG9w0BAQUFADBGMQswCQYD -VQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZR29vZ2xlIElu -dGVybmV0IEF1dGhvcml0eTAeFw0xMzA1MjIxNTQ5MDRaFw0xMzEwMzEyMzU5NTla -MGYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N -b3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRUwEwYDVQQDFAwqLmdv -b2dsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARmSpIUbCqhUBq1UwnR -Ai7/TNSk6W8JmasR+I0r/NLDYv5yApbAz8HXXN8hDdurMRP6Jy1Q0UIKmyls8HPH -exoCo4IECjCCBAYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAsGA1Ud -DwQEAwIHgDAdBgNVHQ4EFgQUU3jT0NVNRgU5ZinRHGrlyoGEnoYwHwYDVR0jBBgw -FoAUv8Aw6/VDET5nup6R+/xq2uNrEiQwWwYDVR0fBFQwUjBQoE6gTIZKaHR0cDov -L3d3dy5nc3RhdGljLmNvbS9Hb29nbGVJbnRlcm5ldEF1dGhvcml0eS9Hb29nbGVJ -bnRlcm5ldEF1dGhvcml0eS5jcmwwZgYIKwYBBQUHAQEEWjBYMFYGCCsGAQUFBzAC -hkpodHRwOi8vd3d3LmdzdGF0aWMuY29tL0dvb2dsZUludGVybmV0QXV0aG9yaXR5 -L0dvb2dsZUludGVybmV0QXV0aG9yaXR5LmNydDAMBgNVHRMBAf8EAjAAMIICwwYD -VR0RBIICujCCAraCDCouZ29vZ2xlLmNvbYINKi5hbmRyb2lkLmNvbYIWKi5hcHBl -bmdpbmUuZ29vZ2xlLmNvbYISKi5jbG91ZC5nb29nbGUuY29tghYqLmdvb2dsZS1h -bmFseXRpY3MuY29tggsqLmdvb2dsZS5jYYILKi5nb29nbGUuY2yCDiouZ29vZ2xl -LmNvLmlugg4qLmdvb2dsZS5jby5qcIIOKi5nb29nbGUuY28udWuCDyouZ29vZ2xl -LmNvbS5hcoIPKi5nb29nbGUuY29tLmF1gg8qLmdvb2dsZS5jb20uYnKCDyouZ29v -Z2xlLmNvbS5jb4IPKi5nb29nbGUuY29tLm14gg8qLmdvb2dsZS5jb20udHKCDyou -Z29vZ2xlLmNvbS52boILKi5nb29nbGUuZGWCCyouZ29vZ2xlLmVzggsqLmdvb2ds -ZS5mcoILKi5nb29nbGUuaHWCCyouZ29vZ2xlLml0ggsqLmdvb2dsZS5ubIILKi5n -b29nbGUucGyCCyouZ29vZ2xlLnB0gg8qLmdvb2dsZWFwaXMuY26CFCouZ29vZ2xl -Y29tbWVyY2UuY29tgg0qLmdzdGF0aWMuY29tggwqLnVyY2hpbi5jb22CECoudXJs -Lmdvb2dsZS5jb22CFioueW91dHViZS1ub2Nvb2tpZS5jb22CDSoueW91dHViZS5j -b22CFioueW91dHViZWVkdWNhdGlvbi5jb22CCyoueXRpbWcuY29tggthbmRyb2lk -LmNvbYIEZy5jb4IGZ29vLmdsghRnb29nbGUtYW5hbHl0aWNzLmNvbYIKZ29vZ2xl -LmNvbYISZ29vZ2xlY29tbWVyY2UuY29tggp1cmNoaW4uY29tggh5b3V0dS5iZYIL -eW91dHViZS5jb22CFHlvdXR1YmVlZHVjYXRpb24uY29tMA0GCSqGSIb3DQEBBQUA -A4GBAAMn0K3j3yhC+X+uyh6eABa2Eq7xiY5/mUB886Ir19vxluSMNKD6n/iY8vHj -trn0BhuW8/vmJyudFkIcEDUYE4ivQMlsfIL7SOGw6OevVLmm02aiRHWj5T20Ds+S -OpueYUG3NBcHP/5IzhUYIQJbGzlQaUaZBMaQeC8ZslMNLWI2 ------END CERTIFICATE-----)"; - - std::string google_cert_key = R"(-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZkqSFGwqoVAatVMJ0QIu/0zUpOlv -CZmrEfiNK/zSw2L+cgKWwM/B11zfIQ3bqzET+ictUNFCCpspbPBzx3saAg== ------END PUBLIC KEY----- -)"; -} \ No newline at end of file diff --git a/src/jwt-cpp/README.md b/src/jwt-cpp/README.md deleted file mode 100644 index 7636f9f67..000000000 --- a/src/jwt-cpp/README.md +++ /dev/null @@ -1,98 +0,0 @@ -# jwt-cpp - -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/5f7055e294744901991fd0a1620b231d)](https://app.codacy.com/app/Thalhammer/jwt-cpp?utm_source=github.com&utm_medium=referral&utm_content=Thalhammer/jwt-cpp&utm_campaign=Badge_Grade_Settings) - -A header only library for creating and validating json web tokens in c++. - -## Signature algorithms -As of version 0.2.0 jwt-cpp supports all algorithms defined by the spec. The modular design of jwt-cpp allows one to add additional algorithms without any problems. If you need any feel free to open a pull request. -For the sake of completeness, here is a list of all supported algorithms: -* HS256 -* HS384 -* HS512 -* RS256 -* RS384 -* RS512 -* ES256 -* ES384 -* ES512 -* PS256 -* PS384 -* PS512 - -## Examples -Simple example of decoding a token and printing all claims: -```c++ -#include -#include - -int main(int argc, const char** argv) { - std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; - auto decoded = jwt::decode(token); - - for(auto& e : decoded.get_payload_claims()) - std::cout << e.first << " = " << e.second.to_json() << std::endl; -} -``` - -In order to verify a token you first build a verifier and use it to verify a decoded token. -```c++ -auto verifier = jwt::verify() - .allow_algorithm(jwt::algorithm::hs256{ "secret" }) - .with_issuer("auth0"); - -verifier.verify(decoded_token); -``` -The created verifier is stateless so you can reuse it for different tokens. - -Creating a token (and signing) is equally easy. -```c++ -auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .set_payload_claim("sample", std::string("test")) - .sign(jwt::algorithm::hs256{"secret"}); -``` - -Here is a simple example of creating a token that will expire in 2 hours: - -```c++ - - // Note to @Thalhammer: please replace with a better example if this is not a good way - auto token = jwt::create() - .set_issuer("auth0") - .set_issued_at(jwt::date(std::chrono::system_clock::now())) - .set_expires_at(jwt::date(std::chrono::system_clock::now()+ std::chrono::seconds{3600})) - .sign(jwt::algorithm::hs256{"secret"} - - -``` - -## Contributing -If you have an improvement or found a bug feel free to [open an issue](https://github.com/Thalhammer/jwt-cpp/issues/new) or add the change and create a pull request. If you file a bug please make sure to include as much information about your environment (compiler version, etc.) as possible to help reproduce the issue. If you add a new feature please make sure to also include test cases for it. - -## Dependencies -In order to use jwt-cpp you need the following tools. -* libcrypto (openssl or compatible) -* libssl-dev (for the header files) -* a compiler supporting at least c++11 -* basic stl support - -In order to build the test cases you also need -* gtest installed in linker path -* pthread - -## Troubleshooting -#### Expired tokens -If you are generating tokens that seem to immediately expire, you are likely not using UTC. Specifically, -if you use `get_time` to get the current time, it likely uses localtime, while this library uses UTC, which may be why your token is immediately expiring. Please see example above on the right way to use current time. - -#### Missing _HMAC amd _EVP_sha256 symbols on Mac -There seems to exists a problem with the included openssl library of MacOS. Make sure you link to one provided by brew. -See [here](https://github.com/Thalhammer/jwt-cpp/issues/6) for more details. -#### Building on windows fails with syntax errors -The header "Windows.h", which is often included in windowsprojects, defines macros for MIN and MAX which screw up std::numeric_limits. -See [here](https://github.com/Thalhammer/jwt-cpp/issues/5) for more details. To fix this do one of the following things: -* define NOMINMAX, which suppresses this behaviour -* include this library before you include windows.h -* place ```#undef max``` and ```#undef min``` before you include this library diff --git a/src/jwt-cpp/TestMain.cpp b/src/jwt-cpp/TestMain.cpp deleted file mode 100644 index b8b7262bc..000000000 --- a/src/jwt-cpp/TestMain.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include - -int main(int argc, char *argv[]) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/src/jwt-cpp/TokenFormatTest.cpp b/src/jwt-cpp/TokenFormatTest.cpp deleted file mode 100644 index e670a82c8..000000000 --- a/src/jwt-cpp/TokenFormatTest.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include "include/jwt-cpp/jwt.h" - -TEST(TokenFormatTest, MissingDot) { - ASSERT_THROW(jwt::decode("eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0.eyJpc3MiOiJhdXRoMCJ9"), std::invalid_argument); - ASSERT_THROW(jwt::decode("eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0eyJpc3MiOiJhdXRoMCJ9."), std::invalid_argument); -} - -TEST(TokenFormatTest, InvalidChar) { - ASSERT_THROW(jwt::decode("eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0().eyJpc3MiOiJhdXRoMCJ9."), std::runtime_error); -} - -TEST(TokenFormatTest, InvalidJSON) { - ASSERT_THROW(jwt::decode("YXsiYWxnIjoibm9uZSIsInR5cCI6IkpXUyJ9YQ.eyJpc3MiOiJhdXRoMCJ9."), std::runtime_error); -} \ No newline at end of file diff --git a/src/jwt-cpp/TokenTest.cpp b/src/jwt-cpp/TokenTest.cpp deleted file mode 100644 index 6d2d004c3..000000000 --- a/src/jwt-cpp/TokenTest.cpp +++ /dev/null @@ -1,420 +0,0 @@ -#include -#include "include/jwt-cpp/jwt.h" - -namespace { - extern std::string rsa_priv_key; - extern std::string rsa_pub_key; - extern std::string rsa_pub_key_invalid; - extern std::string rsa512_priv_key; - extern std::string rsa512_pub_key; - extern std::string rsa512_pub_key_invalid; - extern std::string ecdsa_priv_key; - extern std::string ecdsa_pub_key; - extern std::string ecdsa_pub_key_invalid; -} - -TEST(TokenTest, DecodeToken) { - std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; - auto decoded = jwt::decode(token); - - ASSERT_TRUE(decoded.has_algorithm()); - ASSERT_TRUE(decoded.has_type()); - ASSERT_FALSE(decoded.has_content_type()); - ASSERT_FALSE(decoded.has_key_id()); - ASSERT_TRUE(decoded.has_issuer()); - ASSERT_FALSE(decoded.has_subject()); - ASSERT_FALSE(decoded.has_audience()); - ASSERT_FALSE(decoded.has_expires_at()); - ASSERT_FALSE(decoded.has_not_before()); - ASSERT_FALSE(decoded.has_issued_at()); - ASSERT_FALSE(decoded.has_id()); - - ASSERT_EQ("HS256", decoded.get_algorithm()); - ASSERT_EQ("JWS", decoded.get_type()); - ASSERT_EQ("auth0", decoded.get_issuer()); -} - -TEST(TokenTest, CreateToken) { - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::none{}); - ASSERT_EQ("eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0.eyJpc3MiOiJhdXRoMCJ9.", token); -} - -TEST(TokenTest, CreateTokenHS256) { - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::hs256{"secret"}); - ASSERT_EQ("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE", token); -} - -TEST(TokenTest, CreateTokenRS256) { - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::rs256(rsa_pub_key, rsa_priv_key, "", "")); - - ASSERT_EQ( - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8" - "HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4" - "YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9" - "sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ", token); -} - -TEST(TokenTest, CreateTokenRS512) { - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::rs512(rsa512_pub_key, rsa512_priv_key, "", "")); - - ASSERT_EQ( - "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.GZhnjtsvBl2_KDSxg4JW6xnmNjr2mWhYSZSSQyLKvI0" - "TK86sJKchkt_HDy2IC5l5BGRhq_Xv9pHdA1umidQZG3a7gWvHsujqybCBgBraMTd1wJrCl4QxFg2RYHhHbRqb9BnPJgFD_vryd4GB" - "hfGgejPBCBlGrQtqFGFdHHOjNHY", token); -} - -TEST(TokenTest, CreateTokenPS256) { - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::ps256(rsa_pub_key, rsa_priv_key, "", "")); - - // TODO: Find a better way to check if generated signature is valid - // Can't do simple check for equal since pss adds random salt. -} - -TEST(TokenTest, CreateTokenPS384) { - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::ps384(rsa_pub_key, rsa_priv_key, "", "")); - - // TODO: Find a better way to check if generated signature is valid - // Can't do simple check for equal since pss adds random salt. -} - -TEST(TokenTest, CreateTokenPS512) { - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::ps512(rsa_pub_key, rsa_priv_key, "", "")); - - // TODO: Find a better way to check if generated signature is valid - // Can't do simple check for equal since pss adds random salt. -} - -TEST(TokenTest, CreateTokenES256) { - - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::es256("", ecdsa_priv_key, "", "")); - - auto decoded = jwt::decode(token); - - ASSERT_THROW(jwt::verify().allow_algorithm(jwt::algorithm::es256(ecdsa_pub_key_invalid, "", "", "")).verify(decoded), jwt::signature_verification_exception); - ASSERT_NO_THROW(jwt::verify().allow_algorithm(jwt::algorithm::es256(ecdsa_pub_key, "", "", "")).verify(decoded)); -} - -TEST(TokenTest, CreateTokenES256NoPrivate) { - - ASSERT_THROW([](){ - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::es256(ecdsa_pub_key, "", "", "")); - }(), jwt::signature_generation_exception); -} - -TEST(TokenTest, VerifyTokenRS256) { - std::string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8" - "HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4" - "YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9" - "sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::rs256(rsa_pub_key, rsa_priv_key, "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - verify.verify(decoded_token); -} - -TEST(TokenTest, VerifyTokenRS256PublicOnly) { - std::string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8" - "HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4" - "YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9" - "sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::rs256(rsa_pub_key, "", "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - verify.verify(decoded_token); -} - -TEST(TokenTest, VerifyTokenRS256Fail) { - std::string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8" - "HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4" - "YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9" - "sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::rs256(rsa_pub_key_invalid, "", "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - ASSERT_THROW(verify.verify(decoded_token), jwt::signature_verification_exception); -} - -TEST(TokenTest, VerifyTokenRS512) { - std::string token = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.GZhnjtsvBl2_KDSxg4JW6xnmNjr2mWhYSZ" - "SSQyLKvI0TK86sJKchkt_HDy2IC5l5BGRhq_Xv9pHdA1umidQZG3a7gWvHsujqybCBgBraMTd1wJrCl4QxFg2RYHhHbRqb9BnPJgFD_vryd4" - "GBhfGgejPBCBlGrQtqFGFdHHOjNHY"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::rs512(rsa512_pub_key, rsa512_priv_key, "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - verify.verify(decoded_token); -} - -TEST(TokenTest, VerifyTokenRS512PublicOnly) { - std::string token = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.GZhnjtsvBl2_KDSxg4JW6xnmNjr2mWhYSZ" - "SSQyLKvI0TK86sJKchkt_HDy2IC5l5BGRhq_Xv9pHdA1umidQZG3a7gWvHsujqybCBgBraMTd1wJrCl4QxFg2RYHhHbRqb9BnPJgFD_vryd4" - "GBhfGgejPBCBlGrQtqFGFdHHOjNHY"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::rs512(rsa512_pub_key, "", "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - verify.verify(decoded_token); -} - -TEST(TokenTest, VerifyTokenRS512Fail) { - std::string token = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.GZhnjtsvBl2_KDSxg4JW6xnmNjr2mWhYSZ" - "SSQyLKvI0TK86sJKchkt_HDy2IC5l5BGRhq_Xv9pHdA1umidQZG3a7gWvHsujqybCBgBraMTd1wJrCl4QxFg2RYHhHbRqb9BnPJgFD_vryd4" - "GBhfGgejPBCBlGrQtqFGFdHHOjNHY"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::rs512(rsa_pub_key_invalid, "", "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - ASSERT_THROW(verify.verify(decoded_token), jwt::signature_verification_exception); -} - -TEST(TokenTest, VerifyTokenHS256) { - std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::hs256{ "secret" }) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - verify.verify(decoded_token); -} - -TEST(TokenTest, VerifyFail) { - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::none{}); - - auto decoded_token = jwt::decode(token); - - { - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::none{}) - .with_issuer("auth"); - ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); - } - { - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::none{}) - .with_issuer("auth0") - .with_audience({ "test" }); - ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); - } - { - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::none{}) - .with_issuer("auth0") - .with_subject("test"); - ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); - } - { - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::none{}) - .with_issuer("auth0") - .with_claim("myclaim", jwt::claim(std::string("test"))); - ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); - } -} - -TEST(TokenTest, VerifyTokenES256) { - const std::string token = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g"; - - auto verify = jwt::verify().allow_algorithm(jwt::algorithm::es256(ecdsa_pub_key, "", "", "")); - auto decoded_token = jwt::decode(token); - - verify.verify(decoded_token); -} - -TEST(TokenTest, VerifyTokenES256Fail) { - const std::string token = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::es256(ecdsa_pub_key_invalid, "", "", "")); - auto decoded_token = jwt::decode(token); - - ASSERT_THROW(verify.verify(decoded_token), jwt::signature_verification_exception); -} - -TEST(TokenTest, VerifyTokenPS256) { - std::string token = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.CJ4XjVWdbV6vXGZkD4GdJbtYc80SN9cmPOqRhZBRzOyDRqTFE" - "4MsbdKyQuhAWcvuMOjn-24qOTjVMR_P_uTC1uG6WPLcucxZyLnbb56zbKnEklW2SX0mQnCGewr-93a_vDaFT6Cp45MsF_OwFPRCMaS5CJg-" - "N5KY67UrVSr3s9nkuK9ZTQkyODHfyEUh9F_FhRCATGrb5G7_qHqBYvTvaPUXqzhhpCjN855Tocg7A24Hl0yMwM-XdasucW5xNdKjG_YCkis" - "HX7ax--JiF5GNYCO61eLFteO4THUg-3Z0r4OlGqlppyWo5X5tjcxOZCvBh7WDWfkxA48KFZPRv0nlKA"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::ps256(rsa_pub_key, rsa_priv_key, "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - verify.verify(decoded_token); -} - -TEST(TokenTest, VerifyTokenPS256PublicOnly) { - std::string token = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.CJ4XjVWdbV6vXGZkD4GdJbtYc80SN9cmPOqRhZBRzOyDRqTFE" - "4MsbdKyQuhAWcvuMOjn-24qOTjVMR_P_uTC1uG6WPLcucxZyLnbb56zbKnEklW2SX0mQnCGewr-93a_vDaFT6Cp45MsF_OwFPRCMaS5CJg-" - "N5KY67UrVSr3s9nkuK9ZTQkyODHfyEUh9F_FhRCATGrb5G7_qHqBYvTvaPUXqzhhpCjN855Tocg7A24Hl0yMwM-XdasucW5xNdKjG_YCkis" - "HX7ax--JiF5GNYCO61eLFteO4THUg-3Z0r4OlGqlppyWo5X5tjcxOZCvBh7WDWfkxA48KFZPRv0nlKA"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::ps256(rsa_pub_key, "", "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - verify.verify(decoded_token); -} - -TEST(TokenTest, VerifyTokenPS256Fail) { - std::string token = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.CJ4XjVWdbV6vXGZkD4GdJbtYc80SN9cmPOqRhZBRzOyDRqTFE" - "4MsbdKyQuhAWcvuMOjn-24qOTjVMR_P_uTC1uG6WPLcucxZyLnbb56zbKnEklW2SX0mQnCGewr-93a_vDaFT6Cp45MsF_OwFPRCMaS5CJg-" - "N5KY67UrVSr3s9nkuK9ZTQkyODHfyEUh9F_FhRCATGrb5G7_qHqBYvTvaPUXqzhhpCjN855Tocg7A24Hl0yMwM-XdasucW5xNdKjG_YCkis" - "HX7ax--JiF5GNYCO61eLFteO4THUg-3Z0r4OlGqlppyWo5X5tjcxOZCvBh7WDWfkxA48KFZPRv0nlKA"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::ps256(rsa_pub_key_invalid, "", "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - ASSERT_THROW(verify.verify(decoded_token), jwt::signature_verification_exception); -} - -namespace { - std::string rsa_priv_key = R"(-----BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4ZtdaIrd1BPIJ -tfnF0TjIK5inQAXZ3XlCrUlJdP+XHwIRxdv1FsN12XyMYO/6ymLmo9ryoQeIrsXB -XYqlET3zfAY+diwCb0HEsVvhisthwMU4gZQu6TYW2s9LnXZB5rVtcBK69hcSlA2k -ZudMZWxZcj0L7KMfO2rIvaHw/qaVOE9j0T257Z8Kp2CLF9MUgX0ObhIsdumFRLaL -DvDUmBPr2zuh/34j2XmWwn1yjN/WvGtdfhXW79Ki1S40HcWnygHgLV8sESFKUxxQ -mKvPUTwDOIwLFL5WtE8Mz7N++kgmDcmWMCHc8kcOIu73Ta/3D4imW7VbKgHZo9+K -3ESFE3RjAgMBAAECggEBAJTEIyjMqUT24G2FKiS1TiHvShBkTlQdoR5xvpZMlYbN -tVWxUmrAGqCQ/TIjYnfpnzCDMLhdwT48Ab6mQJw69MfiXwc1PvwX1e9hRscGul36 -ryGPKIVQEBsQG/zc4/L2tZe8ut+qeaK7XuYrPp8bk/X1e9qK5m7j+JpKosNSLgJj -NIbYsBkG2Mlq671irKYj2hVZeaBQmWmZxK4fw0Istz2WfN5nUKUeJhTwpR+JLUg4 -ELYYoB7EO0Cej9UBG30hbgu4RyXA+VbptJ+H042K5QJROUbtnLWuuWosZ5ATldwO -u03dIXL0SH0ao5NcWBzxU4F2sBXZRGP2x/jiSLHcqoECgYEA4qD7mXQpu1b8XO8U -6abpKloJCatSAHzjgdR2eRDRx5PMvloipfwqA77pnbjTUFajqWQgOXsDTCjcdQui -wf5XAaWu+TeAVTytLQbSiTsBhrnoqVrr3RoyDQmdnwHT8aCMouOgcC5thP9vQ8Us -rVdjvRRbnJpg3BeSNimH+u9AHgsCgYEA0EzcbOltCWPHRAY7B3Ge/AKBjBQr86Kv -TdpTlxePBDVIlH+BM6oct2gaSZZoHbqPjbq5v7yf0fKVcXE4bSVgqfDJ/sZQu9Lp -PTeV7wkk0OsAMKk7QukEpPno5q6tOTNnFecpUhVLLlqbfqkB2baYYwLJR3IRzboJ -FQbLY93E8gkCgYB+zlC5VlQbbNqcLXJoImqItgQkkuW5PCgYdwcrSov2ve5r/Acz -FNt1aRdSlx4176R3nXyibQA1Vw+ztiUFowiP9WLoM3PtPZwwe4bGHmwGNHPIfwVG -m+exf9XgKKespYbLhc45tuC08DATnXoYK7O1EnUINSFJRS8cezSI5eHcbQKBgQDC -PgqHXZ2aVftqCc1eAaxaIRQhRmY+CgUjumaczRFGwVFveP9I6Gdi+Kca3DE3F9Pq -PKgejo0SwP5vDT+rOGHN14bmGJUMsX9i4MTmZUZ5s8s3lXh3ysfT+GAhTd6nKrIE -kM3Nh6HWFhROptfc6BNusRh1kX/cspDplK5x8EpJ0QKBgQDWFg6S2je0KtbV5PYe -RultUEe2C0jYMDQx+JYxbPmtcopvZQrFEur3WKVuLy5UAy7EBvwMnZwIG7OOohJb -vkSpADK6VPn9lbqq7O8cTedEHttm6otmLt8ZyEl3hZMaL3hbuRj6ysjmoFKx6CrX -rK0/Ikt5ybqUzKCMJZg2VKGTxg== ------END PRIVATE KEY-----)"; - std::string rsa_pub_key = R"(-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuGbXWiK3dQTyCbX5xdE4 -yCuYp0AF2d15Qq1JSXT/lx8CEcXb9RbDddl8jGDv+spi5qPa8qEHiK7FwV2KpRE9 -83wGPnYsAm9BxLFb4YrLYcDFOIGULuk2FtrPS512Qea1bXASuvYXEpQNpGbnTGVs -WXI9C+yjHztqyL2h8P6mlThPY9E9ue2fCqdgixfTFIF9Dm4SLHbphUS2iw7w1JgT -69s7of9+I9l5lsJ9cozf1rxrXX4V1u/SotUuNB3Fp8oB4C1fLBEhSlMcUJirz1E8 -AziMCxS+VrRPDM+zfvpIJg3JljAh3PJHDiLu902v9w+Iplu1WyoB2aPfitxEhRN0 -YwIDAQAB ------END PUBLIC KEY-----)"; - std::string rsa_pub_key_invalid = R"(-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxzYuc22QSst/dS7geYYK -5l5kLxU0tayNdixkEQ17ix+CUcUbKIsnyftZxaCYT46rQtXgCaYRdJcbB3hmyrOa -vkhTpX79xJZnQmfuamMbZBqitvscxW9zRR9tBUL6vdi/0rpoUwPMEh8+Bw7CgYR0 -FK0DhWYBNDfe9HKcyZEv3max8Cdq18htxjEsdYO0iwzhtKRXomBWTdhD5ykd/fAC -VTr4+KEY+IeLvubHVmLUhbE5NgWXxrRpGasDqzKhCTmsa2Ysf712rl57SlH0Wz/M -r3F7aM9YpErzeYLrl0GhQr9BVJxOvXcVd4kmY+XkiCcrkyS1cnghnllh+LCwQu1s -YwIDAQAB ------END PUBLIC KEY-----)"; - std::string rsa512_priv_key = R"(-----BEGIN RSA PRIVATE KEY----- -MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw -33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW -+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB -AoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS -3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5Cp -uGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE -2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0 -GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0K -Su5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY -6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5 -fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523 -Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP -FaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw== ------END RSA PRIVATE KEY-----)"; - std::string rsa512_pub_key = R"(-----BEGIN PUBLIC KEY----- -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd -UWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs -HUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D -o2kQ+X5xK9cipRgEKwIDAQAB ------END PUBLIC KEY-----)"; - std::string rsa512_pub_key_invalid = R"(-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxzYuc22QSst/dS7geYYK -5l5kLxU0tayNdixkEQ17ix+CUcUbKIsnyftZxaCYT46rQtXgCaYRdJcbB3hmyrOa -vkhTpX79xJZnQmfuamMbZBqitvscxW9zRR9tBUL6vdi/0rpoUwPMEh8+Bw7CgYR0 -FK0DhWYBNDfe9HKcyZEv3max8Cdq18htxjEsdYO0iwzhtKRXomBWTdhD5ykd/fAC -VTr4+KEY+IeLvubHVmLUhbE5NgWXxrRpGasDqzKhCTmsa2Ysf712rl57SlH0Wz/M -r3F7aM9YpErzeYLrl0GhQr9BVJxOvXcVd4kmY+XkiCcrkyS1cnghnllh+LCwQu1s -YwIDAQAB ------END PUBLIC KEY-----)"; - std::string ecdsa_priv_key = R"(-----BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPGJGAm4X1fvBuC1z -SpO/4Izx6PXfNMaiKaS5RUkFqEGhRANCAARCBvmeksd3QGTrVs2eMrrfa7CYF+sX -sjyGg+Bo5mPKGH4Gs8M7oIvoP9pb/I85tdebtKlmiCZHAZE5w4DfJSV6 ------END PRIVATE KEY-----)"; - std::string ecdsa_pub_key = R"(-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQgb5npLHd0Bk61bNnjK632uwmBfr -F7I8hoPgaOZjyhh+BrPDO6CL6D/aW/yPObXXm7SpZogmRwGROcOA3yUleg== ------END PUBLIC KEY-----)"; - std::string ecdsa_pub_key_invalid = R"(-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoBUyo8CQAFPeYPvv78ylh5MwFZjT -CLQeb042TjiMJxG+9DLFmRSMlBQ9T/RsLLc+PmpB1+7yPAR+oR5gZn3kJQ== ------END PUBLIC KEY-----)"; -} diff --git a/src/jwt-cpp/include/jwt-cpp/base.h b/src/jwt-cpp/include/jwt-cpp/base.h deleted file mode 100644 index dfca7fc08..000000000 --- a/src/jwt-cpp/include/jwt-cpp/base.h +++ /dev/null @@ -1,168 +0,0 @@ -#pragma once -#include -#include - -namespace jwt { - namespace alphabet { - struct base64 { - static const std::array& data() { - static std::array data = { - {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}}; - return data; - }; - static const std::string& fill() { - static std::string fill = "="; - return fill; - } - }; - struct base64url { - static const std::array& data() { - static std::array data = { - {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}}; - return data; - }; - static const std::string& fill() { - static std::string fill = "%3d"; - return fill; - } - }; - } - - class base { - public: - template - static std::string encode(const std::string& bin) { - return encode(bin, T::data(), T::fill()); - } - template - static std::string decode(const std::string& base) { - return decode(base, T::data(), T::fill()); - } - - private: - static std::string encode(const std::string& bin, const std::array& alphabet, const std::string& fill) { - size_t size = bin.size(); - std::string res; - - // clear incomplete bytes - size_t fast_size = size - size % 3; - for (size_t i = 0; i < fast_size;) { - uint32_t octet_a = (unsigned char)bin[i++]; - uint32_t octet_b = (unsigned char)bin[i++]; - uint32_t octet_c = (unsigned char)bin[i++]; - - uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; - - res += alphabet[(triple >> 3 * 6) & 0x3F]; - res += alphabet[(triple >> 2 * 6) & 0x3F]; - res += alphabet[(triple >> 1 * 6) & 0x3F]; - res += alphabet[(triple >> 0 * 6) & 0x3F]; - } - - if (fast_size == size) - return res; - - size_t mod = size % 3; - - uint32_t octet_a = fast_size < size ? (unsigned char)bin[fast_size++] : 0; - uint32_t octet_b = fast_size < size ? (unsigned char)bin[fast_size++] : 0; - uint32_t octet_c = fast_size < size ? (unsigned char)bin[fast_size++] : 0; - - uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; - - switch (mod) { - case 1: - res += alphabet[(triple >> 3 * 6) & 0x3F]; - res += alphabet[(triple >> 2 * 6) & 0x3F]; - res += fill; - res += fill; - break; - case 2: - res += alphabet[(triple >> 3 * 6) & 0x3F]; - res += alphabet[(triple >> 2 * 6) & 0x3F]; - res += alphabet[(triple >> 1 * 6) & 0x3F]; - res += fill; - break; - default: - break; - } - - return res; - } - - static std::string decode(const std::string& base, const std::array& alphabet, const std::string& fill) { - size_t size = base.size(); - - size_t fill_cnt = 0; - while (size > fill.size()) { - if (base.substr(size - fill.size(), fill.size()) == fill) { - fill_cnt++; - size -= fill.size(); - if(fill_cnt > 2) - throw std::runtime_error("Invalid input"); - } - else break; - } - - if ((size + fill_cnt) % 4 != 0) - throw std::runtime_error("Invalid input"); - - size_t out_size = size / 4 * 3; - std::string res; - res.reserve(out_size); - - auto get_sextet = [&](size_t offset) { - for (size_t i = 0; i < alphabet.size(); i++) { - if (alphabet[i] == base[offset]) - return i; - } - throw std::runtime_error("Invalid input"); - }; - - - size_t fast_size = size - size % 4; - for (size_t i = 0; i < fast_size;) { - uint32_t sextet_a = get_sextet(i++); - uint32_t sextet_b = get_sextet(i++); - uint32_t sextet_c = get_sextet(i++); - uint32_t sextet_d = get_sextet(i++); - - uint32_t triple = (sextet_a << 3 * 6) - + (sextet_b << 2 * 6) - + (sextet_c << 1 * 6) - + (sextet_d << 0 * 6); - - res += (triple >> 2 * 8) & 0xFF; - res += (triple >> 1 * 8) & 0xFF; - res += (triple >> 0 * 8) & 0xFF; - } - - if (fill_cnt == 0) - return res; - - uint32_t triple = (get_sextet(fast_size) << 3 * 6) - + (get_sextet(fast_size + 1) << 2 * 6); - - switch (fill_cnt) { - case 1: - triple |= (get_sextet(fast_size + 2) << 1 * 6); - res += (triple >> 2 * 8) & 0xFF; - res += (triple >> 1 * 8) & 0xFF; - break; - case 2: - res += (triple >> 2 * 8) & 0xFF; - break; - default: - break; - } - - return res; - } - }; -} diff --git a/src/jwt-cpp/include/jwt-cpp/jwt.h b/src/jwt-cpp/include/jwt-cpp/jwt.h deleted file mode 100644 index c8c3c8719..000000000 --- a/src/jwt-cpp/include/jwt-cpp/jwt.h +++ /dev/null @@ -1,1593 +0,0 @@ -#pragma once -#define PICOJSON_USE_INT64 -#include "picojson.h" -#include "base.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//If openssl version less than 1.1 -#if OPENSSL_VERSION_NUMBER < 269484032 -#define OPENSSL10 -#endif - -#ifndef JWT_CLAIM_EXPLICIT -#define JWT_CLAIM_EXPLICIT 0 -#endif - -namespace jwt { - using date = std::chrono::system_clock::time_point; - - struct signature_verification_exception : public std::runtime_error { - signature_verification_exception() - : std::runtime_error("signature verification failed") - {} - explicit signature_verification_exception(const std::string& msg) - : std::runtime_error(msg) - {} - explicit signature_verification_exception(const char* msg) - : std::runtime_error(msg) - {} - }; - struct signature_generation_exception : public std::runtime_error { - signature_generation_exception() - : std::runtime_error("signature generation failed") - {} - explicit signature_generation_exception(const std::string& msg) - : std::runtime_error(msg) - {} - explicit signature_generation_exception(const char* msg) - : std::runtime_error(msg) - {} - }; - struct rsa_exception : public std::runtime_error { - explicit rsa_exception(const std::string& msg) - : std::runtime_error(msg) - {} - explicit rsa_exception(const char* msg) - : std::runtime_error(msg) - {} - }; - struct ecdsa_exception : public std::runtime_error { - explicit ecdsa_exception(const std::string& msg) - : std::runtime_error(msg) - {} - explicit ecdsa_exception(const char* msg) - : std::runtime_error(msg) - {} - }; - struct token_verification_exception : public std::runtime_error { - token_verification_exception() - : std::runtime_error("token verification failed") - {} - explicit token_verification_exception(const std::string& msg) - : std::runtime_error("token verification failed: " + msg) - {} - }; - - namespace helper { - inline - std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw = "") { - // TODO: Cannot find the exact version this change happended -#if OPENSSL_VERSION_NUMBER <= 0x1000114fL - std::unique_ptr certbio(BIO_new_mem_buf(const_cast(certstr.data()), certstr.size()), BIO_free_all); -#else - std::unique_ptr certbio(BIO_new_mem_buf(certstr.data(), certstr.size()), BIO_free_all); -#endif - std::unique_ptr keybio(BIO_new(BIO_s_mem()), BIO_free_all); - - std::unique_ptr cert(PEM_read_bio_X509(certbio.get(), nullptr, nullptr, const_cast(pw.c_str())), X509_free); - if (!cert) throw rsa_exception("Error loading cert into memory"); - std::unique_ptr key(X509_get_pubkey(cert.get()), EVP_PKEY_free); - if(!key) throw rsa_exception("Error getting public key from certificate"); - if(!PEM_write_bio_PUBKEY(keybio.get(), key.get())) throw rsa_exception("Error writing public key data in PEM format"); - char* ptr = nullptr; - auto len = BIO_get_mem_data(keybio.get(), &ptr); - if(len <= 0 || ptr == nullptr) throw rsa_exception("Failed to convert pubkey to pem"); - std::string res(ptr, len); - return res; - } - } - - namespace algorithm { - /** - * "none" algorithm. - * - * Returns and empty signature and checks if the given signature is empty. - */ - struct none { - /// Return an empty string - std::string sign(const std::string&) const { - return ""; - } - /// Check if the given signature is empty. JWT's with "none" algorithm should not contain a signature. - void verify(const std::string&, const std::string& signature) const { - if (!signature.empty()) - throw signature_verification_exception(); - } - /// Get algorithm name - std::string name() const { - return "none"; - } - }; - /** - * Base class for HMAC family of algorithms - */ - struct hmacsha { - /** - * Construct new hmac algorithm - * \param key Key to use for HMAC - * \param md Pointer to hash function - * \param name Name of the algorithm - */ - hmacsha(std::string key, const EVP_MD*(*md)(), const std::string& name) - : secret(std::move(key)), md(md), alg_name(name) - {} - /** - * Sign jwt data - * \param data The data to sign - * \return HMAC signature for the given data - * \throws signature_generation_exception - */ - std::string sign(const std::string& data) const { - std::string res; - res.resize(EVP_MAX_MD_SIZE); - unsigned int len = res.size(); - if (HMAC(md(), secret.data(), secret.size(), (const unsigned char*)data.data(), data.size(), (unsigned char*)res.data(), &len) == nullptr) - throw signature_generation_exception(); - res.resize(len); - return res; - } - /** - * Check if signature is valid - * \param data The data to check signature against - * \param signature Signature provided by the jwt - * \throws signature_verification_exception If the provided signature does not match - */ - void verify(const std::string& data, const std::string& signature) const { - try { - auto res = sign(data); - bool matched = true; - for (size_t i = 0; i < std::min(res.size(), signature.size()); i++) - if (res[i] != signature[i]) - matched = false; - if (res.size() != signature.size()) - matched = false; - if (!matched) - throw signature_verification_exception(); - } - catch (const signature_generation_exception&) { - throw signature_verification_exception(); - } - } - /** - * Returns the algorithm name provided to the constructor - * \return Algorithmname - */ - std::string name() const { - return alg_name; - } - private: - /// HMAC secrect - const std::string secret; - /// HMAC hash generator - const EVP_MD*(*md)(); - /// Algorithmname - const std::string alg_name; - }; - /** - * Base class for RSA family of algorithms - */ - struct rsa { - /** - * Construct new rsa algorithm - * \param public_key RSA public key in PEM format - * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - * \param md Pointer to hash function - * \param name Name of the algorithm - */ - rsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, const std::string& private_key_password, const EVP_MD*(*md)(), const std::string& name) - : md(md), alg_name(name) - { - - std::unique_ptr pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); - - if(public_key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { - auto pkey = helper::extract_pubkey_from_cert(public_key, public_key_password); - if ((size_t)BIO_write(pubkey_bio.get(), pkey.data(), pkey.size()) != pkey.size()) - throw rsa_exception("failed to load public key: bio_write failed"); - } else { - if ((size_t)BIO_write(pubkey_bio.get(), public_key.data(), public_key.size()) != public_key.size()) - throw rsa_exception("failed to load public key: bio_write failed"); - } - pkey.reset(PEM_read_bio_PUBKEY(pubkey_bio.get(), nullptr, nullptr, (void*)public_key_password.c_str()), EVP_PKEY_free); - if (!pkey) - throw rsa_exception("failed to load public key: PEM_read_bio_PUBKEY failed"); - - if (!private_key.empty()) { - std::unique_ptr privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); - if ((size_t)BIO_write(privkey_bio.get(), private_key.data(), private_key.size()) != private_key.size()) - throw rsa_exception("failed to load private key: bio_write failed"); - RSA* privkey = PEM_read_bio_RSAPrivateKey(privkey_bio.get(), nullptr, nullptr, (void*)private_key_password.c_str()); - if (privkey == nullptr) - throw rsa_exception("failed to load private key: PEM_read_bio_RSAPrivateKey failed"); - if (EVP_PKEY_assign_RSA(pkey.get(), privkey) == 0) { - RSA_free(privkey); - throw rsa_exception("failed to load private key: EVP_PKEY_assign_RSA failed"); - } - } - } - /** - * Sign jwt data - * \param data The data to sign - * \return RSA signature for the given data - * \throws signature_generation_exception - */ - std::string sign(const std::string& data) const { -#ifdef OPENSSL10 - std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_destroy); -#else - std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); -#endif - if (!ctx) - throw signature_generation_exception("failed to create signature: could not create context"); - if (!EVP_SignInit(ctx.get(), md())) - throw signature_generation_exception("failed to create signature: SignInit failed"); - - std::string res; - res.resize(EVP_PKEY_size(pkey.get())); - unsigned int len = 0; - - if (!EVP_SignUpdate(ctx.get(), data.data(), data.size())) - throw signature_generation_exception(); - if (!EVP_SignFinal(ctx.get(), (unsigned char*)res.data(), &len, pkey.get())) - throw signature_generation_exception(); - - res.resize(len); - return res; - } - /** - * Check if signature is valid - * \param data The data to check signature against - * \param signature Signature provided by the jwt - * \throws signature_verification_exception If the provided signature does not match - */ - void verify(const std::string& data, const std::string& signature) const { -#ifdef OPENSSL10 - std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_destroy); -#else - std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); -#endif - if (!ctx) - throw signature_verification_exception("failed to verify signature: could not create context"); - if (!EVP_VerifyInit(ctx.get(), md())) - throw signature_verification_exception("failed to verify signature: VerifyInit failed"); - if (!EVP_VerifyUpdate(ctx.get(), data.data(), data.size())) - throw signature_verification_exception("failed to verify signature: VerifyUpdate failed"); - if (!EVP_VerifyFinal(ctx.get(), (const unsigned char*)signature.data(), signature.size(), pkey.get())) - throw signature_verification_exception(); - } - /** - * Returns the algorithm name provided to the constructor - * \return Algorithmname - */ - std::string name() const { - return alg_name; - } - private: - /// OpenSSL structure containing converted keys - std::shared_ptr pkey; - /// Hash generator - const EVP_MD*(*md)(); - /// Algorithmname - const std::string alg_name; - }; - /** - * Base class for ECDSA family of algorithms - */ - struct ecdsa { - /** - * Construct new ecdsa algorithm - * \param public_key ECDSA public key in PEM format - * \param private_key ECDSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - * \param md Pointer to hash function - * \param name Name of the algorithm - */ - ecdsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, const std::string& private_key_password, const EVP_MD*(*md)(), const std::string& name) - : md(md), alg_name(name) - { - if (private_key.empty()) { - std::unique_ptr pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); - if(public_key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { - auto pkey = helper::extract_pubkey_from_cert(public_key, public_key_password); - if ((size_t)BIO_write(pubkey_bio.get(), pkey.data(), pkey.size()) != pkey.size()) - throw ecdsa_exception("failed to load public key: bio_write failed"); - } else { - if ((size_t)BIO_write(pubkey_bio.get(), public_key.data(), public_key.size()) != public_key.size()) - throw ecdsa_exception("failed to load public key: bio_write failed"); - } - - pkey.reset(PEM_read_bio_EC_PUBKEY(pubkey_bio.get(), nullptr, nullptr, (void*)public_key_password.c_str()), EC_KEY_free); - if (!pkey) - throw ecdsa_exception("failed to load public key: PEM_read_bio_EC_PUBKEY failed"); - } else { - std::unique_ptr privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); - if ((size_t)BIO_write(privkey_bio.get(), private_key.data(), private_key.size()) != private_key.size()) - throw ecdsa_exception("failed to load private key: bio_write failed"); - pkey.reset(PEM_read_bio_ECPrivateKey(privkey_bio.get(), nullptr, nullptr, (void*)private_key_password.c_str()), EC_KEY_free); - if (!pkey) - throw ecdsa_exception("failed to load private key: PEM_read_bio_RSAPrivateKey failed"); - } - - if(EC_KEY_check_key(pkey.get()) == 0) - throw ecdsa_exception("failed to load key: key is invalid"); - } - /** - * Sign jwt data - * \param data The data to sign - * \return ECDSA signature for the given data - * \throws signature_generation_exception - */ - std::string sign(const std::string& data) const { - const std::string hash = generate_hash(data); - - std::unique_ptr - sig(ECDSA_do_sign((const unsigned char*)hash.data(), hash.size(), pkey.get()), ECDSA_SIG_free); - if(!sig) - throw signature_generation_exception(); -#ifdef OPENSSL10 - - return bn2raw(sig->r) + bn2raw(sig->s); -#else - const BIGNUM *r; - const BIGNUM *s; - ECDSA_SIG_get0(sig.get(), &r, &s); - return bn2raw(r) + bn2raw(s); -#endif - } - /** - * Check if signature is valid - * \param data The data to check signature against - * \param signature Signature provided by the jwt - * \throws signature_verification_exception If the provided signature does not match - */ - void verify(const std::string& data, const std::string& signature) const { - const std::string hash = generate_hash(data); - auto r = raw2bn(signature.substr(0, signature.size() / 2)); - auto s = raw2bn(signature.substr(signature.size() / 2)); - -#ifdef OPENSSL10 - ECDSA_SIG sig; - sig.r = r.get(); - sig.s = s.get(); - - if(ECDSA_do_verify((const unsigned char*)hash.data(), hash.size(), &sig, pkey.get()) != 1) - throw signature_verification_exception("Invalid signature"); -#else - ECDSA_SIG *sig = ECDSA_SIG_new(); - - ECDSA_SIG_set0(sig, r.get(), s.get()); - - if(ECDSA_do_verify((const unsigned char*)hash.data(), hash.size(), sig, pkey.get()) != 1) - throw signature_verification_exception("Invalid signature"); -#endif - } - /** - * Returns the algorithm name provided to the constructor - * \return Algorithmname - */ - std::string name() const { - return alg_name; - } - private: - /** - * Convert a OpenSSL BIGNUM to a std::string - * \param bn BIGNUM to convert - * \return bignum as string - */ -#ifdef OPENSSL10 - static std::string bn2raw(BIGNUM* bn) -#else - static std::string bn2raw(const BIGNUM* bn) -#endif - { - std::string res; - res.resize(BN_num_bytes(bn)); - BN_bn2bin(bn, (unsigned char*)res.data()); - if(res.size()%2 == 1 && res[0] == 0x00) - return res.substr(1); - return res; - } - /** - * Convert an std::string to a OpenSSL BIGNUM - * \param raw String to convert - * \return BIGNUM representation - */ - static std::unique_ptr raw2bn(const std::string& raw) { - if(static_cast(raw[0]) >= 0x80) { - std::string str(1, 0x00); - str += raw; - return std::unique_ptr(BN_bin2bn((const unsigned char*)str.data(), str.size(), nullptr), BN_free); - } - return std::unique_ptr(BN_bin2bn((const unsigned char*)raw.data(), raw.size(), nullptr), BN_free); - } - - /** - * Hash the provided data using the hash function specified in constructor - * \param data Data to hash - * \return Hash of data - */ - std::string generate_hash(const std::string& data) const { -#ifdef OPENSSL10 - std::unique_ptr ctx(EVP_MD_CTX_create(), &EVP_MD_CTX_destroy); -#else - std::unique_ptr ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free); -#endif - if(EVP_DigestInit(ctx.get(), md()) == 0) - throw signature_generation_exception("EVP_DigestInit failed"); - if(EVP_DigestUpdate(ctx.get(), data.data(), data.size()) == 0) - throw signature_generation_exception("EVP_DigestUpdate failed"); - unsigned int len = 0; - std::string res; - res.resize(EVP_MD_CTX_size(ctx.get())); - if(EVP_DigestFinal(ctx.get(), (unsigned char*)res.data(), &len) == 0) - throw signature_generation_exception("EVP_DigestFinal failed"); - res.resize(len); - return res; - } - - /// OpenSSL struct containing keys - std::shared_ptr pkey; - /// Hash generator function - const EVP_MD*(*md)(); - /// Algorithmname - const std::string alg_name; - }; - - /** - * Base class for PSS-RSA family of algorithms - */ - struct pss { - /** - * Construct new pss algorithm - * \param public_key RSA public key in PEM format - * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - * \param md Pointer to hash function - * \param name Name of the algorithm - */ - pss(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, const std::string& private_key_password, const EVP_MD*(*md)(), const std::string& name) - : md(md), alg_name(name) - { - std::unique_ptr pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); - if(public_key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { - auto pkey = helper::extract_pubkey_from_cert(public_key, public_key_password); - if ((size_t)BIO_write(pubkey_bio.get(), pkey.data(), pkey.size()) != pkey.size()) - throw rsa_exception("failed to load public key: bio_write failed"); - } else { - if ((size_t)BIO_write(pubkey_bio.get(), public_key.data(), public_key.size()) != public_key.size()) - throw rsa_exception("failed to load public key: bio_write failed"); - } - pkey.reset(PEM_read_bio_PUBKEY(pubkey_bio.get(), nullptr, nullptr, (void*)public_key_password.c_str()), EVP_PKEY_free); - if (!pkey) - throw rsa_exception("failed to load public key: PEM_read_bio_PUBKEY failed"); - - if (!private_key.empty()) { - std::unique_ptr privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); - if ((size_t)BIO_write(privkey_bio.get(), private_key.data(), private_key.size()) != private_key.size()) - throw rsa_exception("failed to load private key: bio_write failed"); - RSA* privkey = PEM_read_bio_RSAPrivateKey(privkey_bio.get(), nullptr, nullptr, (void*)private_key_password.c_str()); - if (privkey == nullptr) - throw rsa_exception("failed to load private key: PEM_read_bio_RSAPrivateKey failed"); - if (EVP_PKEY_assign_RSA(pkey.get(), privkey) == 0) { - RSA_free(privkey); - throw rsa_exception("failed to load private key: EVP_PKEY_assign_RSA failed"); - } - } - } - /** - * Sign jwt data - * \param data The data to sign - * \return ECDSA signature for the given data - * \throws signature_generation_exception - */ - std::string sign(const std::string& data) const { - auto hash = this->generate_hash(data); - - std::unique_ptr key(EVP_PKEY_get1_RSA(pkey.get()), RSA_free); - const int size = RSA_size(key.get()); - - std::string padded(size, 0x00); - if (!RSA_padding_add_PKCS1_PSS_mgf1(key.get(), (unsigned char*)padded.data(), (const unsigned char*)hash.data(), md(), md(), -1)) - throw signature_generation_exception("failed to create signature: RSA_padding_add_PKCS1_PSS_mgf1 failed"); - - std::string res(size, 0x00); - if (RSA_private_encrypt(size, (const unsigned char*)padded.data(), (unsigned char*)res.data(), key.get(), RSA_NO_PADDING) < 0) - throw signature_generation_exception("failed to create signature: RSA_private_encrypt failed"); - return res; - } - /** - * Check if signature is valid - * \param data The data to check signature against - * \param signature Signature provided by the jwt - * \throws signature_verification_exception If the provided signature does not match - */ - void verify(const std::string& data, const std::string& signature) const { - auto hash = this->generate_hash(data); - - std::unique_ptr key(EVP_PKEY_get1_RSA(pkey.get()), RSA_free); - const int size = RSA_size(key.get()); - - std::string sig(size, 0x00); - if(!RSA_public_decrypt(signature.size(), (const unsigned char*)signature.data(), (unsigned char*)sig.data(), key.get(), RSA_NO_PADDING)) - throw signature_verification_exception("Invalid signature"); - - if(!RSA_verify_PKCS1_PSS_mgf1(key.get(), (const unsigned char*)hash.data(), md(), md(), (const unsigned char*)sig.data(), -1)) - throw signature_verification_exception("Invalid signature"); - } - /** - * Returns the algorithm name provided to the constructor - * \return Algorithmname - */ - std::string name() const { - return alg_name; - } - private: - /** - * Hash the provided data using the hash function specified in constructor - * \param data Data to hash - * \return Hash of data - */ - std::string generate_hash(const std::string& data) const { -#ifdef OPENSSL10 - std::unique_ptr ctx(EVP_MD_CTX_create(), &EVP_MD_CTX_destroy); -#else - std::unique_ptr ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free); -#endif - if(EVP_DigestInit(ctx.get(), md()) == 0) - throw signature_generation_exception("EVP_DigestInit failed"); - if(EVP_DigestUpdate(ctx.get(), data.data(), data.size()) == 0) - throw signature_generation_exception("EVP_DigestUpdate failed"); - unsigned int len = 0; - std::string res; - res.resize(EVP_MD_CTX_size(ctx.get())); - if(EVP_DigestFinal(ctx.get(), (unsigned char*)res.data(), &len) == 0) - throw signature_generation_exception("EVP_DigestFinal failed"); - res.resize(len); - return res; - } - - /// OpenSSL structure containing keys - std::shared_ptr pkey; - /// Hash generator function - const EVP_MD*(*md)(); - /// Algorithmname - const std::string alg_name; - }; - - /** - * HS256 algorithm - */ - struct hs256 : public hmacsha { - /** - * Construct new instance of algorithm - * \param key HMAC signing key - */ - explicit hs256(std::string key) - : hmacsha(std::move(key), EVP_sha256, "HS256") - {} - }; - /** - * HS384 algorithm - */ - struct hs384 : public hmacsha { - /** - * Construct new instance of algorithm - * \param key HMAC signing key - */ - explicit hs384(std::string key) - : hmacsha(std::move(key), EVP_sha384, "HS384") - {} - }; - /** - * HS512 algorithm - */ - struct hs512 : public hmacsha { - /** - * Construct new instance of algorithm - * \param key HMAC signing key - */ - explicit hs512(std::string key) - : hmacsha(std::move(key), EVP_sha512, "HS512") - {} - }; - /** - * RS256 algorithm - */ - struct rs256 : public rsa { - /** - * Construct new instance of algorithm - * \param public_key RSA public key in PEM format - * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - rs256(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "RS256") - {} - }; - /** - * RS384 algorithm - */ - struct rs384 : public rsa { - /** - * Construct new instance of algorithm - * \param public_key RSA public key in PEM format - * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - rs384(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "RS384") - {} - }; - /** - * RS512 algorithm - */ - struct rs512 : public rsa { - /** - * Construct new instance of algorithm - * \param public_key RSA public key in PEM format - * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - rs512(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "RS512") - {} - }; - /** - * ES256 algorithm - */ - struct es256 : public ecdsa { - /** - * Construct new instance of algorithm - * \param public_key ECDSA public key in PEM format - * \param private_key ECDSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - es256(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "ES256") - {} - }; - /** - * ES384 algorithm - */ - struct es384 : public ecdsa { - /** - * Construct new instance of algorithm - * \param public_key ECDSA public key in PEM format - * \param private_key ECDSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - es384(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "ES384") - {} - }; - /** - * ES512 algorithm - */ - struct es512 : public ecdsa { - /** - * Construct new instance of algorithm - * \param public_key ECDSA public key in PEM format - * \param private_key ECDSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - es512(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "ES512") - {} - }; - - /** - * PS256 algorithm - */ - struct ps256 : public pss { - /** - * Construct new instance of algorithm - * \param public_key RSA public key in PEM format - * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - ps256(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "PS256") - {} - }; - /** - * PS384 algorithm - */ - struct ps384 : public pss { - /** - * Construct new instance of algorithm - * \param public_key RSA public key in PEM format - * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - ps384(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "PS384") - {} - }; - /** - * PS512 algorithm - */ - struct ps512 : public pss { - /** - * Construct new instance of algorithm - * \param public_key RSA public key in PEM format - * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - ps512(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "PS512") - {} - }; - } - - /** - * Convenience wrapper for JSON value - */ - class claim { - picojson::value val; - public: - enum class type { - null, - boolean, - number, - string, - array, - object, - int64 - }; - - claim() - : val() - {} -#if JWT_CLAIM_EXPLICIT - explicit claim(std::string s) - : val(std::move(s)) - {} - explicit claim(const date& s) - : val(int64_t(std::chrono::system_clock::to_time_t(s))) - {} - explicit claim(const std::set& s) - : val(picojson::array(s.cbegin(), s.cend())) - {} - explicit claim(const picojson::value& val) - : val(val) - {} -#else - claim(std::string s) - : val(std::move(s)) - {} - claim(const date& s) - : val(int64_t(std::chrono::system_clock::to_time_t(s))) - {} - claim(const std::set& s) - : val(picojson::array(s.cbegin(), s.cend())) - {} - claim(const picojson::value& val) - : val(val) - {} -#endif - - /** - * Get wrapped json object - * \return Wrapped json object - */ - picojson::value to_json() const { - return val; - } - - /** - * Get type of contained object - * \return Type - * \throws std::logic_error An internal error occured - */ - type get_type() const { - if (val.is()) return type::null; - else if (val.is()) return type::boolean; - else if (val.is()) return type::int64; - else if (val.is()) return type::number; - else if (val.is()) return type::string; - else if (val.is()) return type::array; - else if (val.is()) return type::object; - else throw std::logic_error("internal error"); - } - - /** - * Get the contained object as a string - * \return content as string - * \throws std::bad_cast Content was not a string - */ - const std::string& as_string() const { - if (!val.is()) - throw std::bad_cast(); - return val.get(); - } - /** - * Get the contained object as a date - * \return content as date - * \throws std::bad_cast Content was not a date - */ - date as_date() const { - return std::chrono::system_clock::from_time_t(as_int()); - } - /** - * Get the contained object as an array - * \return content as array - * \throws std::bad_cast Content was not an array - */ - const picojson::array& as_array() const { - if (!val.is()) - throw std::bad_cast(); - return val.get(); - } - /** - * Get the contained object as a set of strings - * \return content as set of strings - * \throws std::bad_cast Content was not a set - */ - const std::set as_set() const { - std::set res; - for(auto& e : as_array()) { - if(!e.is()) - throw std::bad_cast(); - res.insert(e.get()); - } - return res; - } - /** - * Get the contained object as an integer - * \return content as int - * \throws std::bad_cast Content was not an int - */ - int64_t as_int() const { - if (!val.is()) - throw std::bad_cast(); - return val.get(); - } - /** - * Get the contained object as a bool - * \return content as bool - * \throws std::bad_cast Content was not a bool - */ - bool as_bool() const { - if (!val.is()) - throw std::bad_cast(); - return val.get(); - } - /** - * Get the contained object as a number - * \return content as double - * \throws std::bad_cast Content was not a number - */ - double as_number() const { - if (!val.is()) - throw std::bad_cast(); - return val.get(); - } - }; - - /** - * Base class that represents a token payload. - * Contains Convenience accessors for common claims. - */ - class payload { - protected: - std::unordered_map payload_claims; - public: - /** - * Check if issuer is present ("iss") - * \return true if present, false otherwise - */ - bool has_issuer() const noexcept { return has_payload_claim("iss"); } - /** - * Check if subject is present ("sub") - * \return true if present, false otherwise - */ - bool has_subject() const noexcept { return has_payload_claim("sub"); } - /** - * Check if audience is present ("aud") - * \return true if present, false otherwise - */ - bool has_audience() const noexcept { return has_payload_claim("aud"); } - /** - * Check if expires is present ("exp") - * \return true if present, false otherwise - */ - bool has_expires_at() const noexcept { return has_payload_claim("exp"); } - /** - * Check if not before is present ("nbf") - * \return true if present, false otherwise - */ - bool has_not_before() const noexcept { return has_payload_claim("nbf"); } - /** - * Check if issued at is present ("iat") - * \return true if present, false otherwise - */ - bool has_issued_at() const noexcept { return has_payload_claim("iat"); } - /** - * Check if token id is present ("jti") - * \return true if present, false otherwise - */ - bool has_id() const noexcept { return has_payload_claim("jti"); } - /** - * Get issuer claim - * \return issuer as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - const std::string& get_issuer() const { return get_payload_claim("iss").as_string(); } - /** - * Get subject claim - * \return subject as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - const std::string& get_subject() const { return get_payload_claim("sub").as_string(); } - /** - * Get audience claim - * \return audience as a set of strings - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a set (Should not happen in a valid token) - */ - std::set get_audience() const { - auto aud = get_payload_claim("aud"); - if(aud.get_type() == jwt::claim::type::string) return { aud.as_string()}; - else return aud.as_set(); - } - /** - * Get expires claim - * \return expires as a date in utc - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a date (Should not happen in a valid token) - */ - const date get_expires_at() const { return get_payload_claim("exp").as_date(); } - /** - * Get not valid before claim - * \return nbf date in utc - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a date (Should not happen in a valid token) - */ - const date get_not_before() const { return get_payload_claim("nbf").as_date(); } - /** - * Get issued at claim - * \return issued at as date in utc - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a date (Should not happen in a valid token) - */ - const date get_issued_at() const { return get_payload_claim("iat").as_date(); } - /** - * Get id claim - * \return id as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - const std::string& get_id() const { return get_payload_claim("jti").as_string(); } - /** - * Check if a payload claim is present - * \return true if claim was present, false otherwise - */ - bool has_payload_claim(const std::string& name) const noexcept { return payload_claims.count(name) != 0; } - /** - * Get payload claim - * \return Requested claim - * \throws std::runtime_error If claim was not present - */ - const claim& get_payload_claim(const std::string& name) const { - if (!has_payload_claim(name)) - throw std::runtime_error("claim not found"); - return payload_claims.at(name); - } - /** - * Get all payload claims - * \return map of claims - */ - std::unordered_map get_payload_claims() const { return payload_claims; } - }; - - /** - * Base class that represents a token header. - * Contains Convenience accessors for common claims. - */ - class header { - protected: - std::unordered_map header_claims; - public: - /** - * Check if algortihm is present ("alg") - * \return true if present, false otherwise - */ - bool has_algorithm() const noexcept { return has_header_claim("alg"); } - /** - * Check if type is present ("typ") - * \return true if present, false otherwise - */ - bool has_type() const noexcept { return has_header_claim("typ"); } - /** - * Check if content type is present ("cty") - * \return true if present, false otherwise - */ - bool has_content_type() const noexcept { return has_header_claim("cty"); } - /** - * Check if key id is present ("kid") - * \return true if present, false otherwise - */ - bool has_key_id() const noexcept { return has_header_claim("kid"); } - /** - * Get algorithm claim - * \return algorithm as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - const std::string& get_algorithm() const { return get_header_claim("alg").as_string(); } - /** - * Get type claim - * \return type as a string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - const std::string& get_type() const { return get_header_claim("typ").as_string(); } - /** - * Get content type claim - * \return content type as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - const std::string& get_content_type() const { return get_header_claim("cty").as_string(); } - /** - * Get key id claim - * \return key id as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - const std::string& get_key_id() const { return get_header_claim("kid").as_string(); } - /** - * Check if a header claim is present - * \return true if claim was present, false otherwise - */ - bool has_header_claim(const std::string& name) const noexcept { return header_claims.count(name) != 0; } - /** - * Get header claim - * \return Requested claim - * \throws std::runtime_error If claim was not present - */ - const claim& get_header_claim(const std::string& name) const { - if (!has_header_claim(name)) - throw std::runtime_error("claim not found"); - return header_claims.at(name); - } - /** - * Get all header claims - * \return map of claims - */ - std::unordered_map get_header_claims() const { return header_claims; } - }; - - /** - * Class containing all information about a decoded token - */ - class decoded_jwt : public header, public payload { - protected: - /// Unmodifed token, as passed to constructor - const std::string token; - /// Header part decoded from base64 - std::string header; - /// Unmodified header part in base64 - std::string header_base64; - /// Payload part decoded from base64 - std::string payload; - /// Unmodified payload part in base64 - std::string payload_base64; - /// Signature part decoded from base64 - std::string signature; - /// Unmodified signature part in base64 - std::string signature_base64; - public: - /** - * Constructor - * Parses a given token - * \param token The token to parse - * \throws std::invalid_argument Token is not in correct format - * \throws std::runtime_error Base64 decoding failed or invalid json - */ - explicit decoded_jwt(const std::string& token) - : token(token) - { - auto hdr_end = token.find('.'); - if (hdr_end == std::string::npos) - throw std::invalid_argument("invalid token supplied"); - auto payload_end = token.find('.', hdr_end + 1); - if (payload_end == std::string::npos) - throw std::invalid_argument("invalid token supplied"); - header = header_base64 = token.substr(0, hdr_end); - payload = payload_base64 = token.substr(hdr_end + 1, payload_end - hdr_end - 1); - signature = signature_base64 = token.substr(payload_end + 1); - - // Fix padding: JWT requires padding to get removed - auto fix_padding = [](std::string& str) { - switch (str.size() % 4) { - case 1: - str += alphabet::base64url::fill(); -#ifdef __has_cpp_attribute -#if __has_cpp_attribute(fallthrough) - [[fallthrough]]; -#endif -#endif - case 2: - str += alphabet::base64url::fill(); -#ifdef __has_cpp_attribute -#if __has_cpp_attribute(fallthrough) - [[fallthrough]]; -#endif -#endif - case 3: - str += alphabet::base64url::fill(); -#ifdef __has_cpp_attribute -#if __has_cpp_attribute(fallthrough) - [[fallthrough]]; -#endif -#endif - default: - break; - } - }; - fix_padding(header); - fix_padding(payload); - fix_padding(signature); - - header = base::decode(header); - payload = base::decode(payload); - signature = base::decode(signature); - - auto parse_claims = [](const std::string& str) { - std::unordered_map res; - picojson::value val; - if (!picojson::parse(val, str).empty()) - throw std::runtime_error("Invalid json"); - - for (auto& e : val.get()) { res.insert({ e.first, claim(e.second) }); } - - return res; - }; - - header_claims = parse_claims(header); - payload_claims = parse_claims(payload); - } - - /** - * Get token string, as passed to constructor - * \return token as passed to constructor - */ - const std::string& get_token() const { return token; } - /** - * Get header part as json string - * \return header part after base64 decoding - */ - const std::string& get_header() const { return header; } - /** - * Get payload part as json string - * \return payload part after base64 decoding - */ - const std::string& get_payload() const { return payload; } - /** - * Get signature part as json string - * \return signature part after base64 decoding - */ - const std::string& get_signature() const { return signature; } - /** - * Get header part as base64 string - * \return header part before base64 decoding - */ - const std::string& get_header_base64() const { return header_base64; } - /** - * Get payload part as base64 string - * \return payload part before base64 decoding - */ - const std::string& get_payload_base64() const { return payload_base64; } - /** - * Get signature part as base64 string - * \return signature part before base64 decoding - */ - const std::string& get_signature_base64() const { return signature_base64; } - - }; - - /** - * Builder class to build and sign a new token - * Use jwt::create() to get an instance of this class. - */ - class builder { - std::unordered_map header_claims; - std::unordered_map payload_claims; - - builder() {} - friend builder create(); - public: - /** - * Set a header claim. - * \param id Name of the claim - * \param c Claim to add - * \return *this to allow for method chaining - */ - builder& set_header_claim(const std::string& id, claim c) { header_claims[id] = std::move(c); return *this; } - /** - * Set a payload claim. - * \param id Name of the claim - * \param c Claim to add - * \return *this to allow for method chaining - */ - builder& set_payload_claim(const std::string& id, claim c) { payload_claims[id] = std::move(c); return *this; } - /** - * Set algorithm claim - * You normally don't need to do this, as the algorithm is automatically set if you don't change it. - * \param str Name of algorithm - * \return *this to allow for method chaining - */ - builder& set_algorithm(const std::string& str) { return set_header_claim("alg", claim(str)); } - /** - * Set type claim - * \param str Type to set - * \return *this to allow for method chaining - */ - builder& set_type(const std::string& str) { return set_header_claim("typ", claim(str)); } - /** - * Set content type claim - * \param str Type to set - * \return *this to allow for method chaining - */ - builder& set_content_type(const std::string& str) { return set_header_claim("cty", claim(str)); } - /** - * Set key id claim - * \param str Key id to set - * \return *this to allow for method chaining - */ - builder& set_key_id(const std::string& str) { return set_header_claim("kid", claim(str)); } - /** - * Set issuer claim - * \param str Issuer to set - * \return *this to allow for method chaining - */ - builder& set_issuer(const std::string& str) { return set_payload_claim("iss", claim(str)); } - /** - * Set subject claim - * \param str Subject to set - * \return *this to allow for method chaining - */ - builder& set_subject(const std::string& str) { return set_payload_claim("sub", claim(str)); } - /** - * Set audience claim - * \param l Audience set - * \return *this to allow for method chaining - */ - builder& set_audience(const std::set& l) { return set_payload_claim("aud", claim(l)); } - /** - * Set audience claim - * \param aud Single audience - * \return *this to allow for method chaining - */ - builder& set_audience(const std::string& aud) { return set_payload_claim("aud", claim(aud)); } - /** - * Set expires at claim - * \param d Expires time - * \return *this to allow for method chaining - */ - builder& set_expires_at(const date& d) { return set_payload_claim("exp", claim(d)); } - /** - * Set not before claim - * \param d First valid time - * \return *this to allow for method chaining - */ - builder& set_not_before(const date& d) { return set_payload_claim("nbf", claim(d)); } - /** - * Set issued at claim - * \param d Issued at time, should be current time - * \return *this to allow for method chaining - */ - builder& set_issued_at(const date& d) { return set_payload_claim("iat", claim(d)); } - /** - * Set id claim - * \param str ID to set - * \return *this to allow for method chaining - */ - builder& set_id(const std::string& str) { return set_payload_claim("jti", claim(str)); } - - /** - * Sign token and return result - * \param algo Instance of an algorithm to sign the token with - * \return Final token as a string - */ - template - std::string sign(const T& algo) { - this->set_algorithm(algo.name()); - - picojson::object obj_header; - for (auto& e : header_claims) { - obj_header.insert({ e.first, e.second.to_json() }); - } - picojson::object obj_payload; - for (auto& e : payload_claims) { - obj_payload.insert({ e.first, e.second.to_json() }); - } - - auto encode = [](const std::string& data) { - auto base = base::encode(data); - auto pos = base.find(alphabet::base64url::fill()); - base = base.substr(0, pos); - return base; - }; - - std::string header = encode(picojson::value(obj_header).serialize()); - std::string payload = encode(picojson::value(obj_payload).serialize()); - - std::string token = header + "." + payload; - - return token + "." + encode(algo.sign(token)); - } - }; - - /** - * Verifier class used to check if a decoded token contains all claims required by your application and has a valid signature. - */ - template - class verifier { - struct algo_base { - virtual ~algo_base() {} - virtual void verify(const std::string& data, const std::string& sig) = 0; - }; - template - struct algo : public algo_base { - T alg; - explicit algo(T a) : alg(a) {} - virtual void verify(const std::string& data, const std::string& sig) override { - alg.verify(data, sig); - } - }; - - /// Required claims - std::unordered_map claims; - /// Leeway time for exp, nbf and iat - size_t default_leeway = 0; - /// Instance of clock type - Clock clock; - /// Supported algorithms - std::unordered_map> algs; - public: - /** - * Constructor for building a new verifier instance - * \param c Clock instance - */ - explicit verifier(Clock c) : clock(c) {} - - /** - * Set default leeway to use. - * \param leeway Default leeway to use if not specified otherwise - * \return *this to allow chaining - */ - verifier& leeway(size_t leeway) { default_leeway = leeway; return *this; } - /** - * Set leeway for expires at. - * If not specified the default leeway will be used. - * \param leeway Set leeway to use for expires at. - * \return *this to allow chaining - */ - verifier& expires_at_leeway(size_t leeway) { return with_claim("exp", claim(std::chrono::system_clock::from_time_t(leeway))); } - /** - * Set leeway for not before. - * If not specified the default leeway will be used. - * \param leeway Set leeway to use for not before. - * \return *this to allow chaining - */ - verifier& not_before_leeway(size_t leeway) { return with_claim("nbf", claim(std::chrono::system_clock::from_time_t(leeway))); } - /** - * Set leeway for issued at. - * If not specified the default leeway will be used. - * \param leeway Set leeway to use for issued at. - * \return *this to allow chaining - */ - verifier& issued_at_leeway(size_t leeway) { return with_claim("iat", claim(std::chrono::system_clock::from_time_t(leeway))); } - /** - * Set an issuer to check for. - * Check is casesensitive. - * \param iss Issuer to check for. - * \return *this to allow chaining - */ - verifier& with_issuer(const std::string& iss) { return with_claim("iss", claim(iss)); } - /** - * Set a subject to check for. - * Check is casesensitive. - * \param sub Subject to check for. - * \return *this to allow chaining - */ - verifier& with_subject(const std::string& sub) { return with_claim("sub", claim(sub)); } - /** - * Set an audience to check for. - * If any of the specified audiences is not present in the token the check fails. - * \param aud Audience to check for. - * \return *this to allow chaining - */ - verifier& with_audience(const std::set& aud) { return with_claim("aud", claim(aud)); } - /** - * Set an id to check for. - * Check is casesensitive. - * \param id ID to check for. - * \return *this to allow chaining - */ - verifier& with_id(const std::string& id) { return with_claim("jti", claim(id)); } - /** - * Specify a claim to check for. - * \param name Name of the claim to check for - * \param c Claim to check for - * \return *this to allow chaining - */ - verifier& with_claim(const std::string& name, claim c) { claims[name] = c; return *this; } - - /** - * Add an algorithm available for checking. - * \param alg Algorithm to allow - * \return *this to allow chaining - */ - template - verifier& allow_algorithm(Algorithm alg) { - algs[alg.name()] = std::make_shared>(alg); - return *this; - } - - /** - * Verify the given token. - * \param jwt Token to check - * \throws token_verification_exception Verification failed - */ - void verify(const decoded_jwt& jwt) const { - const std::string data = jwt.get_header_base64() + "." + jwt.get_payload_base64(); - const std::string sig = jwt.get_signature(); - const std::string& algo = jwt.get_algorithm(); - if (algs.count(algo) == 0) - throw token_verification_exception("wrong algorithm"); - algs.at(algo)->verify(data, sig); - - auto assert_claim_eq = [](const decoded_jwt& jwt, const std::string& key, const claim& c) { - if (!jwt.has_payload_claim(key)) - throw token_verification_exception("decoded_jwt is missing " + key + " claim"); - auto& jc = jwt.get_payload_claim(key); - if (jc.get_type() != c.get_type()) - throw token_verification_exception("claim " + key + " type mismatch"); - if (c.get_type() == claim::type::int64) { - if (c.as_date() != jc.as_date()) - throw token_verification_exception("claim " + key + " does not match expected"); - } - else if (c.get_type() == claim::type::array) { - auto s1 = c.as_set(); - auto s2 = jc.as_set(); - if (s1.size() != s2.size()) - throw token_verification_exception("claim " + key + " does not match expected"); - auto it1 = s1.cbegin(); - auto it2 = s2.cbegin(); - while (it1 != s1.cend() && it2 != s2.cend()) { - if (*it1++ != *it2++) - throw token_verification_exception("claim " + key + " does not match expected"); - } - } - else if (c.get_type() == claim::type::string) { - if (c.as_string() != jc.as_string()) - throw token_verification_exception("claim " + key + " does not match expected"); - } - else throw token_verification_exception("internal error"); - }; - - auto time = clock.now(); - - if (jwt.has_expires_at()) { - auto leeway = claims.count("exp") == 1 ? std::chrono::system_clock::to_time_t(claims.at("exp").as_date()) : default_leeway; - auto exp = jwt.get_expires_at(); - if (time > exp + std::chrono::seconds(leeway)) - throw token_verification_exception("token expired"); - } - if (jwt.has_issued_at()) { - auto leeway = claims.count("iat") == 1 ? std::chrono::system_clock::to_time_t(claims.at("iat").as_date()) : default_leeway; - auto iat = jwt.get_issued_at(); - if (time < iat - std::chrono::seconds(leeway)) - throw token_verification_exception("token expired"); - } - if (jwt.has_not_before()) { - auto leeway = claims.count("nbf") == 1 ? std::chrono::system_clock::to_time_t(claims.at("nbf").as_date()) : default_leeway; - auto nbf = jwt.get_not_before(); - if (time < nbf - std::chrono::seconds(leeway)) - throw token_verification_exception("token expired"); - } - for (auto& c : claims) - { - if (c.first == "exp" || c.first == "iat" || c.first == "nbf") { - // Nothing to do here, already checked - } - else if (c.first == "aud") { - if (!jwt.has_audience()) - throw token_verification_exception("token doesn't contain the required audience"); - auto aud = jwt.get_audience(); - auto expected = c.second.as_set(); - for (auto& e : expected) - if (aud.count(e) == 0) - throw token_verification_exception("token doesn't contain the required audience"); - } - else { - assert_claim_eq(jwt, c.first, c.second); - } - } - } - }; - - /** - * Create a verifier using the given clock - * \param c Clock instance to use - * \return verifier instance - */ - template - verifier verify(Clock c) { - return verifier(c); - } - - /** - * Default clock class using std::chrono::system_clock as a backend. - */ - struct default_clock { - std::chrono::system_clock::time_point now() const { - return std::chrono::system_clock::now(); - } - }; - - /** - * Create a verifier using the default clock - * \return verifier instance - */ - inline - verifier verify() { - return verify({}); - } - - /** - * Return a builder instance to create a new token - */ - inline - builder create() { - return builder(); - } - - /** - * Decode a token - * \param token Token to decode - * \return Decoded token - * \throws std::invalid_argument Token is not in correct format - * \throws std::runtime_error Base64 decoding failed or invalid json - */ - inline - decoded_jwt decode(const std::string& token) { - return decoded_jwt(token); - } -} diff --git a/src/jwt-cpp/jwt-cpp.sln b/src/jwt-cpp/jwt-cpp.sln deleted file mode 100644 index ef5abc2a6..000000000 --- a/src/jwt-cpp/jwt-cpp.sln +++ /dev/null @@ -1,28 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jwt-cpp", "jwt-cpp.vcxproj", "{1CA8C676-7F8E-434C-9069-8F20A562E6E9}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Debug|x64.ActiveCfg = Debug|x64 - {1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Debug|x64.Build.0 = Debug|x64 - {1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Debug|x86.ActiveCfg = Debug|Win32 - {1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Debug|x86.Build.0 = Debug|Win32 - {1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Release|x64.ActiveCfg = Release|x64 - {1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Release|x64.Build.0 = Release|x64 - {1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Release|x86.ActiveCfg = Release|Win32 - {1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/src/jwt-cpp/jwt-cpp.vcxproj b/src/jwt-cpp/jwt-cpp.vcxproj deleted file mode 100644 index 7d791a4d4..000000000 --- a/src/jwt-cpp/jwt-cpp.vcxproj +++ /dev/null @@ -1,160 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - {1CA8C676-7F8E-434C-9069-8F20A562E6E9} - Win32Proj - jwtcpp - 8.1 - - - - Application - true - v140 - Unicode - - - Application - false - v140 - true - Unicode - - - Application - true - v140 - Unicode - - - Application - false - v140 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - - - true - - - false - - - false - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - Console - true - gtest.lib;gtest_main.lib;%(AdditionalDependencies) - - - - - - - Level3 - Disabled - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - Console - true - gtest.lib;gtest_main.lib;%(AdditionalDependencies) - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - Console - true - true - true - gtest.lib;gtest_main.lib;%(AdditionalDependencies) - - - - - Level3 - - - MaxSpeed - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - Console - true - true - true - gtest.lib;gtest_main.lib;%(AdditionalDependencies) - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/jwt-cpp/jwt-cpp.vcxproj.filters b/src/jwt-cpp/jwt-cpp.vcxproj.filters deleted file mode 100644 index 6419b2d22..000000000 --- a/src/jwt-cpp/jwt-cpp.vcxproj.filters +++ /dev/null @@ -1,36 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Headerdateien - - - Headerdateien - - - Headerdateien - - - - - Quelldateien - - - Quelldateien - - - \ No newline at end of file diff --git a/src/jwt-cpp/vcpkg/CONTROL b/src/jwt-cpp/vcpkg/CONTROL deleted file mode 100644 index d29834fc6..000000000 --- a/src/jwt-cpp/vcpkg/CONTROL +++ /dev/null @@ -1,3 +0,0 @@ -Source: jwt-cpp -Version: 2019-04-20 -Description: A header only library for creating and validating json web tokens in c++ \ No newline at end of file diff --git a/src/jwt-cpp/vcpkg/fix-picojson.patch b/src/jwt-cpp/vcpkg/fix-picojson.patch deleted file mode 100644 index 44c04fe58..000000000 --- a/src/jwt-cpp/vcpkg/fix-picojson.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h -index ec56810..a26fd97 100644 ---- a/include/jwt-cpp/jwt.h -+++ b/include/jwt-cpp/jwt.h -@@ -1,6 +1,6 @@ - #pragma once - #define PICOJSON_USE_INT64 --#include "picojson.h" -+#include "picojson/picojson.h" - #include "base.h" - #include - #include diff --git a/src/jwt-cpp/vcpkg/fix-warning.patch b/src/jwt-cpp/vcpkg/fix-warning.patch deleted file mode 100644 index d013a7782..000000000 --- a/src/jwt-cpp/vcpkg/fix-warning.patch +++ /dev/null @@ -1,31 +0,0 @@ -diff --git a/include/jwt-cpp/base.h b/include/jwt-cpp/base.h -index dfca7fc..4d05c0b 100644 ---- a/include/jwt-cpp/base.h -+++ b/include/jwt-cpp/base.h -@@ -2,6 +2,10 @@ - #include - #include - -+#ifdef _MSC_VER -+#pragma warning(disable : 4267) -+#endif -+ - namespace jwt { - namespace alphabet { - struct base64 { -diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h -index ec56810..313cef2 100644 ---- a/include/jwt-cpp/jwt.h -+++ b/include/jwt-cpp/jwt.h -@@ -12,6 +12,11 @@ - #include - #include - -+#ifdef _MSC_VER -+#pragma warning(disable : 4267) -+#pragma warning(disable : 4067) -+#endif -+ - //If openssl version less than 1.1 - #if OPENSSL_VERSION_NUMBER < 269484032 - #define OPENSSL10 diff --git a/src/jwt-cpp/vcpkg/portfile.cmake b/src/jwt-cpp/vcpkg/portfile.cmake deleted file mode 100644 index 1e10e3c21..000000000 --- a/src/jwt-cpp/vcpkg/portfile.cmake +++ /dev/null @@ -1,23 +0,0 @@ -#header-only library -include(vcpkg_common_functions) - -set(SOURCE_PATH ${CURRENT_BUILDTREES_DIR}/src/jwt-cpp) - -vcpkg_from_github(OUT_SOURCE_PATH SOURCE_PATH - REPO Thalhammer/jwt-cpp - REF f0e37a79f605312686065405dd720fc197cc3df0 - SHA512 ae83c205dbb340dedc58d0d3f0e2453c4edcf5ce43b401f49d02692dc8a2a4b7260f1ced05ddfa7c1d5d6f92446e232629ddbdf67a58a119b50c5c8163591598 - HEAD_REF master - PATCHES fix-picojson.patch - fix-warning.patch) - -# Copy the constexpr header files -file(GLOB HEADER_FILES ${SOURCE_PATH}/include/jwt-cpp/*) -file(COPY ${HEADER_FILES} - DESTINATION ${CURRENT_PACKAGES_DIR}/include/jwt-cpp - REGEX "\.(gitattributes|gitignore|picojson.h)$" EXCLUDE) - -# Put the licence file where vcpkg expects it -file(COPY ${SOURCE_PATH}/LICENSE - DESTINATION ${CURRENT_PACKAGES_DIR}/share/jwt-cpp) -file(RENAME ${CURRENT_PACKAGES_DIR}/share/jwt-cpp/LICENSE ${CURRENT_PACKAGES_DIR}/share/jwt-cpp/copyright) \ No newline at end of file diff --git a/src/libbcrypt/CMakeLists.txt b/src/libbcrypt/CMakeLists.txt deleted file mode 100644 index a0883eec5..000000000 --- a/src/libbcrypt/CMakeLists.txt +++ /dev/null @@ -1,90 +0,0 @@ -################################################################################### -# -# Copyright (c) 2014, webvariants GmbH, http://www.webvariants.de -# -# This file is released under the terms of the MIT license. You can find the -# complete text in the attached LICENSE file or online at: -# -# http://www.opensource.org/licenses/mit-license.php -# -# @author: Tino Rusch (tino.rusch@webvariants.de) -# -################################################################################### - -cmake_minimum_required(VERSION 2.8 FATAL_ERROR) - -project(bcrypt) - -enable_language(ASM) - -set(MYLIB_VERSION_MAJOR 1) -set(MYLIB_VERSION_MINOR 0) -set(MYLIB_VERSION_PATCH 0) -set(MYLIB_VERSION_STRING ${MYLIB_VERSION_MAJOR}.${MYLIB_VERSION_MINOR}.${MYLIB_VERSION_PATCH}) - -# just doing cmake . will build a shared or static lib and honor existing environment setting -# to force build static, cmake . -DBUILD_SHARED_LIBS=Off -# to force build shared, cmake . -DBUILD_SHARED_LIBS=On - -if (NOT BUILD_SHARED_LIBS) - message ("Building a static library") -else () - message ("Building a shared library") -endif () - - -set( CMAKE_COLOR_MAKEFILE ON ) - -set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall --std=c++11 -O3" ) -set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -O3" ) - -set( CMAKE_ASM_FLAGS "${CXXFLAGS} -x assembler-with-cpp") - -set( SRCFILES - ${CMAKE_CURRENT_SOURCE_DIR}/src/bcrypt.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/crypt_blowfish.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/crypt_gensalt.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/wrapper.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/x86.S -) - -include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/include/bcrypt) -include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/include) - -add_library( - ${PROJECT_NAME} - ${SRCFILES} -) - -set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${MYLIB_VERSION_STRING} SOVERSION ${MYLIB_VERSION_MAJOR}) - -set_target_properties(${PROJECT_NAME} PROPERTIES PUBLIC_HEADER include/bcrypt/BCrypt.hpp) - -target_include_directories(${PROJECT_NAME} PRIVATE include) -target_include_directories(${PROJECT_NAME} PRIVATE src) - -add_executable( ${PROJECT_NAME}_test ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp) - -target_link_libraries( ${PROJECT_NAME}_test ${PROJECT_NAME}) - -include(GNUInstallDirs) - -install(TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR} - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/bcrypt) - -install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} - FILES_MATCHING PATTERN "*.h") - -SET(CPACK_GENERATOR "DEB") -SET(CPACK_SET_DESTDIR ON) - -SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "Manuel Romei") -SET(CPACK_PACKAGE_VERSION "1.0.0") -SET(CPACK_PACKAGE_VERSION_MAJOR "1") -SET(CPACK_PACKAGE_VERSION_MINOR "0") -SET(CPACK_PACKAGE_VERSION_PATCH "0") - -INCLUDE(CPack) diff --git a/src/zm.cpp b/src/zm.cpp index e25ce45ce..e541b322f 100644 --- a/src/zm.cpp +++ b/src/zm.cpp @@ -20,4 +20,4 @@ #include "zm.h" /* This is our argv[0], we need it for backtrace */ -const char* self = 0; +const char* self = nullptr; diff --git a/src/zm.h b/src/zm.h index ad0f89584..46d6edf08 100644 --- a/src/zm.h +++ b/src/zm.h @@ -21,17 +21,6 @@ #ifndef ZM_H #define ZM_H -#include "zm_config.h" -#ifdef SOLARIS -#undef DEFAULT_TYPE // pthread defines this which breaks StreamType DEFAULT_TYPE -#include // define strerror() and friends -#endif -#include "zm_logger.h" - -#include - -#include - -extern const char* self; +extern const char *self; #endif // ZM_H diff --git a/src/zm_analysis_thread.cpp b/src/zm_analysis_thread.cpp new file mode 100644 index 000000000..a6f1afcc2 --- /dev/null +++ b/src/zm_analysis_thread.cpp @@ -0,0 +1,36 @@ +#include "zm_analysis_thread.h" + +#include "zm_monitor.h" +#include "zm_signal.h" +#include "zm_time.h" + +AnalysisThread::AnalysisThread(Monitor *monitor) : + monitor_(monitor), terminate_(false) { + thread_ = std::thread(&AnalysisThread::Run, this); +} + +AnalysisThread::~AnalysisThread() { + Stop(); + if (thread_.joinable()) thread_.join(); +} + +void AnalysisThread::Start() { + if (thread_.joinable()) thread_.join(); + terminate_ = false; + Debug(3, "Starting analysis thread"); + thread_ = std::thread(&AnalysisThread::Run, this); +} + +void AnalysisThread::Run() { + while (!(terminate_ or zm_terminate)) { + // Some periodic updates are required for variable capturing framerate + if (!monitor_->Analyse()) { + if (!(terminate_ or zm_terminate)) { + // We only sleep when Analyse returns false because it is an error condition and we will spin like mad if it persists. + 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); + } + } + } +} diff --git a/src/zm_analysis_thread.h b/src/zm_analysis_thread.h new file mode 100644 index 000000000..f949aa729 --- /dev/null +++ b/src/zm_analysis_thread.h @@ -0,0 +1,29 @@ +#ifndef ZM_ANALYSIS_THREAD_H +#define ZM_ANALYSIS_THREAD_H + +#include +#include +#include + +class Monitor; + +class AnalysisThread { + public: + explicit AnalysisThread(Monitor *monitor); + ~AnalysisThread(); + AnalysisThread(AnalysisThread &rhs) = delete; + AnalysisThread(AnalysisThread &&rhs) = delete; + + void Start(); + void Stop() { terminate_ = true; } + bool Stopped() const { return terminate_; } + + private: + void Run(); + + Monitor *monitor_; + std::atomic terminate_; + std::thread thread_; +}; + +#endif diff --git a/src/zm_bigfont.h b/src/zm_bigfont.h deleted file mode 100644 index 96ba479dd..000000000 --- a/src/zm_bigfont.h +++ /dev/null @@ -1,6155 +0,0 @@ -/***********************************************************/ -/* */ -/* Font file generated by schrorg */ -/* based on the font file generated by rthelen */ -/* using utils/mk_bigfont.pl */ -/* */ -/***********************************************************/ - -static unsigned int bigfontdata[] = { - - /* 0 0x00 '^A' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 1 0x01 '^B' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 2 0x02 '^C' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 3 0x03 '^D' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 4 0x04 '^E' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 5 0x05 '^F' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 6 0x06 '^G' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 7 0x07 '^H' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 8 0x08 '^I' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 9 0x09 '^J' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 10 0x0a '^K' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 11 0x0b '^L' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 12 0x0c '^M' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 13 0x0d '^N' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 14 0x0e '^O' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 15 0x0f '^P' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 16 0x10 '^Q' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 17 0x11 '^R' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 18 0x12 '^S' */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 19 0x13 '^T' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 20 0x14 '^U' */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 21 0x15 '^V' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 22 0x16 '^W' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 23 0x17 '^X' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 24 0x18 '^Y' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 25 0x19 '^Z' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 26 0x1a '^[' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 27 0x1b '^\' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 28 0x1c '^]' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 29 0x1d '^^' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 30 0x1e '^_' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 31 0x1f '^`' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 32 0x20 ' ' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 33 0x21 '!' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 34 0x22 '"' */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 35 0x23 '#' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 36 0x24 '$' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 37 0x25 '%' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 38 0x26 '&' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 39 0x27 ''' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 40 0x28 '(' */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 41 0x29 ')' */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 42 0x2a '*' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 43 0x2b '+' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 44 0x2c ',' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 45 0x2d '-' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 46 0x2e '.' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 47 0x2f '/' */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 48 0x30 '0' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 49 0x31 '1' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x3f0, /* 000000 0000 */ - 0x3f0, /* 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 50 0x32 '2' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 51 0x33 '3' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 52 0x34 '4' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x3f0, /* 000000 0000 */ - 0x3f0, /* 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 53 0x35 '5' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 54 0x36 '6' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 55 0x37 '7' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 56 0x38 '8' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 57 0x39 '9' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 58 0x3a ':' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 59 0x3b ';' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 60 0x3c '<' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 61 0x3d '=' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 62 0x3e '>' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 63 0x3f '?' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 64 0x40 '@' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3f30, /* 00 00 0000 */ - 0x3f30, /* 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 65 0x41 'A' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 66 0x42 'B' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 67 0x43 'C' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 68 0x44 'D' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 69 0x45 'E' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 70 0x46 'F' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 71 0x47 'G' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 72 0x48 'H' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 73 0x49 'I' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 74 0x4a 'J' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 75 0x4b 'K' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3c00, /* 00 0000000000 */ - 0x3c00, /* 00 0000000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 76 0x4c 'L' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 77 0x4d 'M' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3cf0, /* 00 00 0000 */ - 0x3cf0, /* 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 78 0x4e 'N' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 79 0x4f 'O' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 80 0x50 'P' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 81 0x51 'Q' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 82 0x52 'R' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 83 0x53 'S' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 84 0x54 'T' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 85 0x55 'U' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 86 0x56 'V' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 87 0x57 'W' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3cf0, /* 00 00 0000 */ - 0x3cf0, /* 00 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 88 0x58 'X' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 89 0x59 'Y' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 90 0x5a 'Z' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 91 0x5b '[' */ - 0xf0, /* 00000000 0000 */ - 0xf0, /* 00000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xf0, /* 00000000 0000 */ - 0xf0, /* 00000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 92 0x5c '\' */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc, /* 000000000000 00 */ - 0xc, /* 000000000000 00 */ - 0xc, /* 000000000000 00 */ - 0xc, /* 000000000000 00 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 93 0x5d ']' */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 94 0x5e '^' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 95 0x5f '_' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ffc, /* 00 00 */ - 0x3ffc, /* 00 00 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 96 0x60 '`' */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 97 0x61 'a' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 98 0x62 'b' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 99 0x63 'c' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 100 0x64 'd' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 101 0x65 'e' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 102 0x66 'f' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf0, /* 00000000 0000 */ - 0xf0, /* 00000000 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 103 0x67 'g' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 104 0x68 'h' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 105 0x69 'i' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 106 0x6a 'j' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3c00, /* 00 0000000000 */ - 0x3c00, /* 00 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 107 0x6b 'k' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3f00, /* 00 00000000 */ - 0x3f00, /* 00 00000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 108 0x6c 'l' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 109 0x6d 'm' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 110 0x6e 'n' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 111 0x6f 'o' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 112 0x70 'p' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 113 0x71 'q' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 114 0x72 'r' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 115 0x73 's' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 116 0x74 't' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xf0, /* 00000000 0000 */ - 0xf0, /* 00000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 117 0x75 'u' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 118 0x76 'v' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 119 0x77 'w' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 120 0x78 'x' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 121 0x79 'y' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 122 0x7a 'z' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 123 0x7b '{' */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 124 0x7c '|' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 125 0x7d '}' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 126 0x7e '~' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 127 0x7f '^?' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 128 0x80 '\200' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 129 0x81 '\201' */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 130 0x82 '\202' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 131 0x83 '\203' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 132 0x84 '\204' */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 133 0x85 '\205' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 134 0x86 '\206' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 135 0x87 '\207' */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 136 0x88 '\210' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 137 0x89 '\211' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 138 0x8a '\212' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 139 0x8b '\213' */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 140 0x8c '\214' */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 141 0x8d '\215' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 142 0x8e '\216' */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 143 0x8f '\217' */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 144 0x90 '\220' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 145 0x91 '\221' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 146 0x92 '\222' */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 147 0x93 '\223' */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 148 0x94 '\224' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 149 0x95 '\225' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 150 0x96 '\226' */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 151 0x97 '\227' */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 152 0x98 '\230' */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 153 0x99 '\231' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 154 0x9a '\232' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 155 0x9b '\233' */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 156 0x9c '\234' */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 157 0x9d '\235' */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 158 0x9e '\236' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 159 0x9f '\237' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 160 0xa0 '\240' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 161 0xa1 '\241' */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 162 0xa2 '\242' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 163 0xa3 '\243' */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3f00, /* 00 00000000 */ - 0x3f00, /* 00 00000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 164 0xa4 '\244' */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 165 0xa5 '\245' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 166 0xa6 '\246' */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 167 0xa7 '\247' */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 168 0xa8 '\250' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3f00, /* 00 00000000 */ - 0x3f00, /* 00 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 169 0xa9 '\251' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3f00, /* 00 00000000 */ - 0x3f00, /* 00 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x3f00, /* 00 00000000 */ - 0x3f00, /* 00 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 170 0xaa '\252' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3fcc, /* 00 00 00 */ - 0x3fcc, /* 00 00 00 */ - 0xcfc, /* 0000 00 00 */ - 0xcfc, /* 0000 00 00 */ - 0xcfc, /* 0000 00 00 */ - 0xcfc, /* 0000 00 00 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 171 0xab '\253' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 172 0xac '\254' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 173 0xad '\255' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 174 0xae '\256' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x33f0, /* 00 00 0000 */ - 0x33f0, /* 00 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 175 0xaf '\257' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 176 0xb0 '\260' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3cf0, /* 00 00 0000 */ - 0x3cf0, /* 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3cf0, /* 00 00 0000 */ - 0x3cf0, /* 00 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 177 0xb1 '\261' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 178 0xb2 '\262' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 179 0xb3 '\263' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3f0, /* 000000 0000 */ - 0x3f0, /* 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 180 0xb4 '\264' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 181 0xb5 '\265' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3f30, /* 00 00 0000 */ - 0x3f30, /* 00 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 182 0xb6 '\266' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xf0, /* 00000000 0000 */ - 0xf0, /* 00000000 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 183 0xb7 '\267' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 184 0xb8 '\270' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 185 0xb9 '\271' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 186 0xba '\272' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3c00, /* 00 0000000000 */ - 0x3c00, /* 00 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 187 0xbb '\273' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3f0, /* 000000 0000 */ - 0x3f0, /* 000000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x3f0, /* 000000 0000 */ - 0x3f0, /* 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 188 0xbc '\274' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 189 0xbd '\275' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3cf0, /* 00 00 0000 */ - 0x3cf0, /* 00 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 190 0xbe '\276' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x33f0, /* 00 00 0000 */ - 0x33f0, /* 00 00 0000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 191 0xbf '\277' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 192 0xc0 '\300' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 193 0xc1 '\301' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 194 0xc2 '\302' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 195 0xc3 '\303' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf0, /* 00000000 0000 */ - 0xf0, /* 00000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 196 0xc4 '\304' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3c00, /* 00 0000000000 */ - 0x3c00, /* 00 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 197 0xc5 '\305' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 198 0xc6 '\306' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 199 0xc7 '\307' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 200 0xc8 '\310' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 201 0xc9 '\311' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 202 0xca '\312' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 203 0xcb '\313' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 204 0xcc '\314' */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 205 0xcd '\315' */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 206 0xce '\316' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 207 0xcf '\317' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x33f0, /* 00 00 0000 */ - 0x33f0, /* 00 00 0000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0xcf0, /* 0000 00 0000 */ - 0xcf0, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 208 0xd0 '\320' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 209 0xd1 '\321' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ffc, /* 00 00 */ - 0x3ffc, /* 00 00 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 210 0xd2 '\322' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 211 0xd3 '\323' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 212 0xd4 '\324' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 213 0xd5 '\325' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 214 0xd6 '\326' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 215 0xd7 '\327' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 216 0xd8 '\330' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 217 0xd9 '\331' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ffc, /* 00 00 */ - 0x3ffc, /* 00 00 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ffc, /* 00 00 */ - 0x3ffc, /* 00 00 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ffc, /* 00 00 */ - 0x3ffc, /* 00 00 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 218 0xda '\332' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 219 0xdb '\333' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 220 0xdc '\334' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 221 0xdd '\335' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 222 0xde '\336' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 223 0xdf '\337' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 224 0xe0 '\340' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 225 0xe1 '\341' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 226 0xe2 '\342' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 227 0xe3 '\343' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 228 0xe4 '\344' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 229 0xe5 '\345' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 230 0xe6 '\346' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 231 0xe7 '\347' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 232 0xe8 '\350' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 233 0xe9 '\351' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 234 0xea '\352' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 235 0xeb '\353' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 236 0xec '\354' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 237 0xed '\355' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 238 0xee '\356' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 239 0xef '\357' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 240 0xf0 '\360' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 241 0xf1 '\361' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 242 0xf2 '\362' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 243 0xf3 '\363' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 244 0xf4 '\364' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 245 0xf5 '\365' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 246 0xf6 '\366' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 247 0xf7 '\367' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 248 0xf8 '\370' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 249 0xf9 '\371' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 250 0xfa '\372' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 251 0xfb '\373' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 252 0xfc '\374' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 253 0xfd '\375' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 254 0xfe '\376' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 255 0xff '\377' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - -}; diff --git a/src/zm_box.h b/src/zm_box.h index fa377e593..22210f2f8 100644 --- a/src/zm_box.h +++ b/src/zm_box.h @@ -20,55 +20,59 @@ #ifndef ZM_BOX_H #define ZM_BOX_H -#include "zm.h" -#include "zm_coord.h" - -#ifndef SOLARIS -#include -#else +#include "zm_line.h" +#include "zm_vector2.h" #include -#endif +#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; +class Box { + 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 LoY() const { return( lo.Y() ); } - inline const Coord &Hi() const { return( hi ); } - inline int HiX() const { return( hi.X() ); } - inline int HiY() const { return( 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(round(lo.X()+(size.X()/2.0))); - int mid_y = int(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 73c9603e1..c65d8b24a 100644 --- a/src/zm_buffer.cpp +++ b/src/zm_buffer.cpp @@ -17,65 +17,77 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include -#include - -#include "zm.h" #include "zm_buffer.h" -unsigned int Buffer::assign( const unsigned char *pStorage, unsigned int pSize ) -{ - if ( mAllocation < pSize ) - { +#include + +unsigned int Buffer::assign(const unsigned char *pStorage, unsigned int pSize) { + if ( mAllocation < pSize ) { delete[] mStorage; mAllocation = pSize; mHead = mStorage = new unsigned char[pSize]; } mSize = pSize; - memcpy( mStorage, pStorage, mSize ); + memcpy(mStorage, pStorage, mSize); mHead = mStorage; mTail = mHead + mSize; - return( mSize ); + return mSize; } -unsigned int Buffer::expand( unsigned int count ) -{ +unsigned int Buffer::expand(unsigned int count) { int spare = mAllocation - mSize; int headSpace = mHead - mStorage; int tailSpace = spare - headSpace; int width = mTail - mHead; - if ( spare > (int)count ) - { - if ( tailSpace < (int)count ) - { - memmove( mStorage, mHead, mSize ); + if ( spare >= static_cast(count) ) { + // There is enough space in the allocation might need to shift everything over though + // + if ( tailSpace < static_cast(count) ) { + // if there is extra space at the head, shift everything over + memmove(mStorage, mHead, mSize); mHead = mStorage; mTail = mHead + width; } - } - else - { + } else { mAllocation += count; unsigned char *newStorage = new unsigned char[mAllocation]; - if ( mStorage ) - { - memcpy( newStorage, mHead, mSize ); + if ( mStorage ) { + memcpy(newStorage, mHead, mSize); delete[] mStorage; } mStorage = newStorage; mHead = mStorage; mTail = mHead + width; } - return( mSize ); + return mSize; } -int Buffer::read_into( int sd, unsigned int bytes ) { +int Buffer::read_into(int sd, unsigned int bytes) { // Make sure there is enough space this->expand(bytes); - int bytes_read = read( sd, mTail, bytes ); - if ( bytes_read > 0 ) { + Debug(3, "Reading %u bytes", 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 8f47c7cbc..fddd2f3cc 100644 --- a/src/zm_buffer.h +++ b/src/zm_buffer.h @@ -20,159 +20,175 @@ #ifndef ZM_BUFFER_H #define ZM_BUFFER_H -#include "zm.h" +#include "zm_logger.h" +#include "zm_time.h" +#include -#include - -class Buffer -{ -protected: +class Buffer { + protected: unsigned char *mStorage; unsigned int mAllocation; unsigned int mSize; unsigned char *mHead; unsigned char *mTail; -public: - Buffer() : mStorage( 0 ), mAllocation( 0 ), mSize( 0 ), mHead( 0 ), mTail( 0 ) { + public: + Buffer() : + mStorage(nullptr), + mAllocation(0), + mSize(0), + mHead(nullptr), + mTail(nullptr) { } - explicit Buffer( unsigned int pSize ) : mAllocation( pSize ), mSize( 0 ) { + explicit Buffer(unsigned int pSize) : mAllocation(pSize), mSize(0) { mHead = mStorage = new unsigned char[mAllocation]; + if (mAllocation) *mHead = '\0'; mTail = mHead; } - Buffer( const unsigned char *pStorage, unsigned int pSize ) : mAllocation( pSize ), mSize( pSize ) { + Buffer(const unsigned char *pStorage, unsigned int pSize) : + mAllocation(pSize), mSize(pSize) { mHead = mStorage = new unsigned char[mSize]; - memcpy( mStorage, pStorage, mSize ); + std::memcpy(mStorage, pStorage, mSize); mTail = mHead + mSize; } - Buffer( const Buffer &buffer ) : mAllocation( buffer.mSize ), mSize( buffer.mSize ) { + Buffer(const Buffer &buffer) : + mAllocation(buffer.mSize), + mSize(buffer.mSize) { mHead = mStorage = new unsigned char[mSize]; - memcpy( mStorage, buffer.mHead, mSize ); + std::memcpy(mStorage, buffer.mHead, mSize); mTail = mHead + mSize; } ~Buffer() { delete[] mStorage; } - unsigned char *head() const { return( mHead ); } - unsigned char *tail() const { return( mTail ); } - unsigned int size() const { return( mSize ); } + unsigned char *head() const { return mHead; } + unsigned char *tail() const { return mTail; } + unsigned int size() const { return mSize; } bool empty() const { return( mSize == 0 ); } unsigned int size( unsigned int pSize ) { if ( mSize < pSize ) { - expand( pSize-mSize ); + expand(pSize-mSize); } - return( mSize ); + return mSize; } - //unsigned int Allocation() const { return( mAllocation ); } + // unsigned int Allocation() const { return( mAllocation ); } void clear() { mSize = 0; mHead = mTail = mStorage; } - unsigned int assign( const unsigned char *pStorage, unsigned int pSize ); - unsigned int assign( const Buffer &buffer ) { - return( assign( buffer.mHead, buffer.mSize ) ); + unsigned int assign(const unsigned char *pStorage, unsigned int pSize); + unsigned int assign(const Buffer &buffer) { + return assign(buffer.mHead, buffer.mSize); } // Trim from the front of the buffer - unsigned int consume( unsigned int count ) { - if ( count > mSize ) { - Warning( "Attempt to consume %d bytes of buffer, size is only %d bytes", count, mSize ); + unsigned int consume(unsigned int count) { + if (count > mSize) { + Warning("Attempt to consume %d bytes of buffer, size is only %d bytes", + count, mSize); count = mSize; } mHead += count; mSize -= count; - tidy( 0 ); - return( count ); + tidy(0); + return count; } // Trim from the end of the buffer - unsigned int shrink( unsigned int count ) { - if ( count > mSize ) { - Warning( "Attempt to shrink buffer by %d bytes, size is only %d bytes", count, mSize ); + unsigned int shrink(unsigned int count) { + if (count > mSize) { + Warning("Attempt to shrink buffer by %d bytes, size is only %d bytes", + count, mSize); count = mSize; } mSize -= count; - if ( mTail > (mHead + mSize) ) + if (mTail > (mHead + mSize)) mTail = mHead + mSize; - tidy( 0 ); - return( count ); + tidy(0); + return count; } // Add to the end of the buffer - unsigned int expand( unsigned int count ); + unsigned int expand(unsigned int count); // Return pointer to the first pSize bytes and advance the head - unsigned char *extract( unsigned int pSize ) { - if ( pSize > mSize ) { - Warning( "Attempt to extract %d bytes of buffer, size is only %d bytes", pSize, mSize ); + // We don't call tidy here because it is assumed the ram will be used afterwards + // This differs from consume + unsigned char *extract(unsigned int pSize) { + if (pSize > mSize) { + Warning("Attempt to extract %d bytes of buffer, size is only %d bytes", + pSize, mSize); pSize = mSize; } unsigned char *oldHead = mHead; mHead += pSize; mSize -= pSize; - tidy( 0 ); - return( oldHead ); + return oldHead; } // Add bytes to the end of the buffer - unsigned int append( const unsigned char *pStorage, unsigned int pSize ) { - expand( pSize ); - memcpy( mTail, pStorage, pSize ); + unsigned int append(const unsigned char *pStorage, unsigned int pSize) { + expand(pSize); + std::memcpy(mTail, pStorage, pSize); mTail += pSize; mSize += pSize; - return( mSize ); + return mSize; } - unsigned int append( const char *pStorage, unsigned int pSize ) { - return( append( (const unsigned char *)pStorage, pSize ) ); + unsigned int append(const char *pStorage, unsigned int pSize) { + return append((const unsigned char *)pStorage, pSize); } - unsigned int append( const Buffer &buffer ) { - return( append( buffer.mHead, buffer.mSize ) ); + unsigned int append(const Buffer &buffer) { + return append(buffer.mHead, buffer.mSize); } - void tidy( bool level=0 ) { - if ( mHead != mStorage ) { - if ( mSize == 0 ) + void tidy(bool level=0) { + if (mHead != mStorage) { + if (mSize == 0) { mHead = mTail = mStorage; - else if ( level ) { - if ( ((uintptr_t)mHead-(uintptr_t)mStorage) > mSize ) { - memcpy( mStorage, mHead, mSize ); +//*mHead = '\0'; + } else if (level) { + if (((uintptr_t)mHead-(uintptr_t)mStorage) > mSize) { + std::memcpy(mStorage, mHead, mSize); mHead = mStorage; mTail = mHead + mSize; } } + } else if (mSize == 0) { + *mHead = '\0'; } } - Buffer &operator=( const Buffer &buffer ) { - assign( buffer ); - return( *this ); + Buffer &operator=(const Buffer &buffer) { + assign(buffer); + return *this; } - Buffer &operator+=( const Buffer &buffer ) { - append( buffer ); - return( *this ); + Buffer &operator+=(const Buffer &buffer) { + append(buffer); + return *this; } - Buffer &operator+=( unsigned int count ) { - expand( count ); - return( *this ); + Buffer &operator+=(unsigned int count) { + expand(count); + return *this; } - Buffer &operator-=( unsigned int count ) { - consume( count ); - return( *this ); + Buffer &operator-=(unsigned int count) { + consume(count); + return *this; } operator unsigned char *() const { - return( mHead ); + return mHead; } operator char *() const { - return( (char *)mHead ); + return reinterpret_cast(mHead); } unsigned char *operator+(int offset) const { - return( (unsigned char *)(mHead+offset) ); + return (unsigned char *)(mHead+offset); } unsigned char operator[](int index) const { - return( *(mHead+index) ); + return *(mHead+index); } operator int () const { - return( (int)mSize ); + return static_cast(mSize); } - int read_into( int sd, unsigned int bytes ); + int read_into(int sd, unsigned int bytes); + int read_into(int sd, unsigned int bytes, Microseconds timeout); }; #endif // ZM_BUFFER_H diff --git a/src/zm_camera.cpp b/src/zm_camera.cpp index 420d5961a..a73961ed9 100644 --- a/src/zm_camera.cpp +++ b/src/zm_camera.cpp @@ -17,11 +17,12 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" #include "zm_camera.h" +#include "zm_monitor.h" + Camera::Camera( - unsigned int p_monitor_id, + const Monitor *monitor, SourceType p_type, unsigned int p_width, unsigned int p_height, @@ -34,39 +35,71 @@ Camera::Camera( bool p_capture, bool p_record_audio ) : - monitor_id(p_monitor_id), + monitor(monitor), type(p_type), width(p_width), height(p_height), colours(p_colours), - subpixelorder(p_subpixelorder), + subpixelorder(p_subpixelorder), brightness(p_brightness), hue(p_hue), colour(p_colour), contrast(p_contrast), capture(p_capture), record_audio(p_record_audio), + mVideoStreamId(-1), + mAudioStreamId(-1), + mVideoCodecContext(nullptr), + mAudioCodecContext(nullptr), + mVideoStream(nullptr), + mAudioStream(nullptr), + mFormatContext(nullptr), + mSecondFormatContext(nullptr), + mFirstVideoPTS(0), + mFirstAudioPTS(0), + mLastVideoPTS(0), + mLastAudioPTS(0), bytes(0) { + linesize = width * colours; pixels = width * height; - imagesize = pixels * colours; + imagesize = height * linesize; - Debug(2,"New camera id: %d width: %d height: %d colours: %d subpixelorder: %d capture: %d", - monitor_id, width, height, colours, subpixelorder, capture); - - monitor = NULL; + Debug(2, "New camera id: %d width: %d line size: %d height: %d colours: %d subpixelorder: %d capture: %d", + monitor->Id(), width, linesize, height, colours, subpixelorder, capture); } Camera::~Camera() { + if ( mFormatContext ) { + // Should also free streams + avformat_free_context(mFormatContext); + } + if ( mSecondFormatContext ) { + // Should also free streams + avformat_free_context(mSecondFormatContext); + } + mVideoStream = nullptr; + mAudioStream = nullptr; } -Monitor *Camera::getMonitor() { - if ( ! monitor ) - monitor = Monitor::Load(monitor_id, false, Monitor::QUERY); - return monitor; -} - -void Camera::setMonitor(Monitor *p_monitor) { - monitor = p_monitor; - monitor_id = monitor->Id(); +AVStream *Camera::getVideoStream() { + if ( !mVideoStream ) { + if ( !mFormatContext ) + mFormatContext = avformat_alloc_context(); + Debug(1, "Allocating avstream"); + mVideoStream = avformat_new_stream(mFormatContext, nullptr); + if ( mVideoStream ) { + mVideoStream->time_base = (AVRational){1, 1000000}; // microseconds as base frame rate + 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 { + Error("Can't create video stream"); + } + mVideoStreamId = mVideoStream->index; + } + return mVideoStream; } diff --git a/src/zm_camera.h b/src/zm_camera.h index a6f576af2..2caa8312d 100644 --- a/src/zm_camera.h +++ b/src/zm_camera.h @@ -20,14 +20,14 @@ #ifndef ZM_CAMERA_H #define ZM_CAMERA_H -#include -#include - #include "zm_image.h" +#include +#include -class Camera; +#include -#include "zm_monitor.h" +class Monitor; +class ZMPacket; // // Abstract base class for cameras. This is intended just to express @@ -35,12 +35,12 @@ class Camera; // class Camera { protected: - typedef enum { LOCAL_SRC, REMOTE_SRC, FILE_SRC, FFMPEG_SRC, LIBVLC_SRC, CURL_SRC } SourceType; + typedef enum { LOCAL_SRC, REMOTE_SRC, FILE_SRC, FFMPEG_SRC, LIBVLC_SRC, CURL_SRC, VNC_SRC } SourceType; - unsigned int monitor_id; - Monitor * monitor; // Null on instantiation, set as soon as possible. + const Monitor *monitor; SourceType type; unsigned int width; + unsigned int linesize; unsigned int height; unsigned int colours; unsigned int subpixelorder; @@ -52,15 +52,37 @@ protected: int contrast; bool capture; bool record_audio; + int mVideoStreamId; + int mAudioStreamId; + AVCodecContext *mVideoCodecContext; + AVCodecContext *mAudioCodecContext; + AVStream *mVideoStream; + AVStream *mAudioStream; + AVFormatContext *mFormatContext; // One for video, one for audio + AVFormatContext *mSecondFormatContext; // One for video, one for audio + int64_t mFirstVideoPTS; + int64_t mFirstAudioPTS; + int64_t mLastVideoPTS; + int64_t mLastAudioPTS; unsigned int bytes; public: - Camera( unsigned int p_monitor_id, SourceType p_type, unsigned int p_width, unsigned int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); + Camera( + const Monitor* monitor, + SourceType p_type, + unsigned int p_width, + unsigned int p_height, + int p_colours, + int p_subpixelorder, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio + ); virtual ~Camera(); - unsigned int getId() const { return monitor_id; } - Monitor *getMonitor(); - void setMonitor( Monitor *p_monitor ); SourceType Type() const { return type; } bool IsLocal() const { return type == LOCAL_SRC; } bool IsRemote() const { return type == REMOTE_SRC; } @@ -68,13 +90,17 @@ public: bool IsFfmpeg() const { return type == FFMPEG_SRC; } bool IsLibvlc() const { return type == LIBVLC_SRC; } bool IscURL() const { return type == CURL_SRC; } + bool IsVNC() const { return type == VNC_SRC; } unsigned int Width() const { return width; } + unsigned int LineSize() const { return linesize; } unsigned int Height() const { return height; } unsigned int Colours() const { return colours; } unsigned int SubpixelOrder() const { return subpixelorder; } 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; } @@ -88,11 +114,17 @@ public: //return (type == FFMPEG_SRC )||(type == REMOTE_SRC); } + virtual AVStream *getVideoStream(); + virtual AVStream *getAudioStream() { return mAudioStream; }; + virtual AVCodecContext *getVideoCodecContext() { return mVideoCodecContext; }; + virtual AVCodecContext *getAudioCodecContext() { return mAudioCodecContext; }; + int getVideoStreamId() { return mVideoStreamId; }; + int getAudioStreamId() { return mAudioStreamId; }; + virtual int PrimeCapture() { return 0; } virtual int PreCapture() = 0; - virtual int Capture(Image &image) = 0; + virtual int Capture(std::shared_ptr &p) = 0; virtual int PostCapture() = 0; - virtual int CaptureAndRecord(Image &image, timeval recording, char* event_directory) = 0; virtual int Close() = 0; }; diff --git a/src/zm_comms.cpp b/src/zm_comms.cpp index 4744388af..2f57f9506 100644 --- a/src/zm_comms.cpp +++ b/src/zm_comms.cpp @@ -20,858 +20,711 @@ #include "zm_comms.h" #include "zm_logger.h" - -#include +#include // for debug output +#include +#include +#include // for snprintf #include -#include -//#include -#if defined(BSD) -#include -#else -#include -#endif - -//#include +#include #include #include -#include -#include // for debug output -#include // for snprintf +#include #ifdef SOLARIS #include // define FIONREAD #endif -int CommsBase::readV( int iovcnt, /* const void *, int, */ ... ) { +int zm::CommsBase::readV(int iovcnt, /* const void *, int, */ ...) { va_list arg_ptr; - struct iovec iov[iovcnt]; - //struct iovec *iov = (struct iovec *)alloca( sizeof(struct iovec)*iovcnt ); + std::vector iov(iovcnt); - va_start( arg_ptr, iovcnt ); - for ( int i = 0; i < iovcnt; i++ ) - { - iov[i].iov_base = va_arg( arg_ptr, void * ); - iov[i].iov_len = va_arg( arg_ptr, int ); + va_start(arg_ptr, iovcnt); + for (int i = 0; i < iovcnt; i++) { + iov[i].iov_base = va_arg(arg_ptr, void *); + iov[i].iov_len = va_arg(arg_ptr, int); } - va_end( arg_ptr ); + va_end(arg_ptr); - int nBytes = ::readv( mRd, iov, iovcnt ); - if ( nBytes < 0 ) - Debug( 1, "Readv of %d buffers max on rd %d failed: %s", iovcnt, mRd, strerror(errno) ); - return( nBytes ); + int nBytes = ::readv(mRd, iov.data(), iovcnt); + if (nBytes < 0) { + Debug(1, "Readv of %d buffers max on rd %d failed: %s", iovcnt, mRd, strerror(errno)); + } + return nBytes; } -int CommsBase::writeV( int iovcnt, /* const void *, int, */ ... ) { +int zm::CommsBase::writeV(int iovcnt, /* const void *, int, */ ...) { va_list arg_ptr; - struct iovec iov[iovcnt]; - //struct iovec *iov = (struct iovec *)alloca( sizeof(struct iovec)*iovcnt ); + std::vector iov(iovcnt); - va_start( arg_ptr, iovcnt ); - for ( int i = 0; i < iovcnt; i++ ) - { - iov[i].iov_base = va_arg( arg_ptr, void * ); - iov[i].iov_len = va_arg( arg_ptr, int ); + va_start(arg_ptr, iovcnt); + for (int i = 0; i < iovcnt; i++) { + iov[i].iov_base = va_arg(arg_ptr, void *); + iov[i].iov_len = va_arg(arg_ptr, int); } - va_end( arg_ptr ); + va_end(arg_ptr); - ssize_t nBytes = ::writev( mWd, iov, iovcnt ); - if ( nBytes < 0 ) - Debug( 1, "Writev of %d buffers on wd %d failed: %s", iovcnt, mWd, strerror(errno) ); - return( nBytes ); + ssize_t nBytes = ::writev(mWd, iov.data(), iovcnt); + if (nBytes < 0) { + Debug(1, "Writev of %d buffers on wd %d failed: %s", iovcnt, mWd, strerror(errno)); + } + return nBytes; } -bool Pipe::open() -{ - if ( ::pipe( mFd ) < 0 ) - { - Error( "pipe(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); +bool zm::Pipe::open() { + if (::pipe(mFd) < 0) { + Error("pipe(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - return( true ); + return true; } -bool Pipe::close() -{ - if ( mFd[0] > -1 ) ::close( mFd[0] ); +bool zm::Pipe::close() { + if (mFd[0] > -1) { + ::close(mFd[0]); + } mFd[0] = -1; - if ( mFd[1] > -1 ) ::close( mFd[1] ); + if (mFd[1] > -1) { + ::close(mFd[1]); + } mFd[1] = -1; - return( true ); + return true; } -bool Pipe::setBlocking( bool blocking ) -{ +bool zm::Pipe::setBlocking(bool blocking) { int flags; /* Now set it for non-blocking I/O */ - if ( (flags = fcntl( mFd[1], F_GETFL )) < 0 ) - { - Error( "fcntl(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if ((flags = fcntl(mFd[1], F_GETFL)) < 0) { + Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - if ( blocking ) - { + if (blocking) { flags &= ~O_NONBLOCK; - } - else - { + } else { flags |= O_NONBLOCK; } - if ( fcntl( mFd[1], F_SETFL, flags ) < 0 ) - { - Error( "fcntl(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if (fcntl(mFd[1], F_SETFL, flags) < 0) { + Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - return( true ); + return true; } -SockAddr::SockAddr( const struct sockaddr *addr ) : mAddr( addr ) -{ +zm::SockAddr *zm::SockAddr::newSockAddr(const sockaddr &addr, socklen_t len) { + if ((addr.sa_family == AF_INET) && (len == SockAddrInet::addrSize())) { + return new SockAddrInet((const sockaddr_in *) &addr); + } else if ((addr.sa_family == AF_UNIX) && (len == SockAddrUnix::addrSize())) { + return new SockAddrUnix((const sockaddr_un *) &addr); + } + + Error("Unable to create new SockAddr from addr family %d with size %d", addr.sa_family, len); + return nullptr; } -SockAddr *SockAddr::newSockAddr( const struct sockaddr &addr, socklen_t len ) -{ - if ( addr.sa_family == AF_INET && len == SockAddrInet::addrSize() ) - { - return( new SockAddrInet( (const struct sockaddr_in *)&addr ) ); +zm::SockAddr *zm::SockAddr::newSockAddr(const SockAddr *addr) { + if (!addr) { + return nullptr; } - else if ( addr.sa_family == AF_UNIX && len == SockAddrUnix::addrSize() ) - { - return( new SockAddrUnix( (const struct sockaddr_un *)&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 with size %d", addr.sa_family, len ); - return( 0 ); + + Error("Unable to create new SockAddr from addr family %d", addr->getDomain()); + return nullptr; } -SockAddr *SockAddr::newSockAddr( const SockAddr *addr ) -{ - if ( !addr ) - return( 0 ); +bool zm::SockAddrInet::resolve(const char *host, const char *serv, const char *proto) { + memset(&mAddrIn, 0, sizeof(mAddrIn)); - 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( 0 ); -} - -SockAddrInet::SockAddrInet() : SockAddr( (struct sockaddr *)&mAddrIn ) -{ -} - -bool SockAddrInet::resolve( const char *host, const char *serv, const char *proto ) -{ - memset( &mAddrIn, 0, sizeof(mAddrIn) ); - - struct hostent *hostent=0; - if ( !(hostent = ::gethostbyname( host ) ) ) - { - Error( "gethostbyname( %s ), h_errno = %d", host, h_errno ); - return( false ); + hostent *hostent = nullptr; + if (!(hostent = ::gethostbyname(host))) { + Error("gethostbyname(%s), h_errno = %d", host, h_errno); + return false; } - struct servent *servent=0; - if ( !(servent = ::getservbyname( serv, proto ) ) ) - { - Error( "getservbyname( %s ), errno = %d, error = %s", serv, errno, strerror(errno) ); - return( false ); + servent *servent = nullptr; + if (!(servent = ::getservbyname(serv, proto))) { + Error("getservbyname( %s ), errno = %d, error = %s", serv, errno, strerror(errno)); + return false; } mAddrIn.sin_port = servent->s_port; mAddrIn.sin_family = AF_INET; - mAddrIn.sin_addr.s_addr = ((struct in_addr *)(hostent->h_addr))->s_addr; + mAddrIn.sin_addr.s_addr = ((in_addr *) (hostent->h_addr))->s_addr; - return( true ); + return true; } -bool SockAddrInet::resolve( const char *host, int port, const char *proto ) -{ - memset( &mAddrIn, 0, sizeof(mAddrIn) ); +bool zm::SockAddrInet::resolve(const char *host, int port, const char *proto) { + memset(&mAddrIn, 0, sizeof(mAddrIn)); - struct hostent *hostent=0; - if ( !(hostent = ::gethostbyname( host ) ) ) - { - Error( "gethostbyname( %s ), h_errno = %d", host, h_errno ); - return( false ); + hostent *hostent = nullptr; + if (!(hostent = ::gethostbyname(host))) { + Error("gethostbyname(%s), h_errno = %d", host, h_errno); + return false; } mAddrIn.sin_port = htons(port); mAddrIn.sin_family = AF_INET; - mAddrIn.sin_addr.s_addr = ((struct in_addr *)(hostent->h_addr))->s_addr; - return( true ); + mAddrIn.sin_addr.s_addr = ((in_addr *) (hostent->h_addr))->s_addr; + return true; } -bool SockAddrInet::resolve( const char *serv, const char *proto ) -{ - memset( &mAddrIn, 0, sizeof(mAddrIn) ); +bool zm::SockAddrInet::resolve(const char *serv, const char *proto) { + memset(&mAddrIn, 0, sizeof(mAddrIn)); - struct servent *servent=0; - if ( !(servent = ::getservbyname( serv, proto ) ) ) - { - Error( "getservbyname( %s ), errno = %d, error = %s", serv, errno, strerror(errno) ); - return( false ); + servent *servent = nullptr; + if (!(servent = ::getservbyname(serv, proto))) { + Error("getservbyname(%s), errno = %d, error = %s", serv, errno, strerror(errno)); + return false; } mAddrIn.sin_port = servent->s_port; mAddrIn.sin_family = AF_INET; mAddrIn.sin_addr.s_addr = INADDR_ANY; - return( true ); + return true; } -bool SockAddrInet::resolve( int port, const char *proto ) -{ - memset( &mAddrIn, 0, sizeof(mAddrIn) ); +bool zm::SockAddrInet::resolve(int port, const char *proto) { + memset(&mAddrIn, 0, sizeof(mAddrIn)); mAddrIn.sin_port = htons(port); mAddrIn.sin_family = AF_INET; mAddrIn.sin_addr.s_addr = INADDR_ANY; - return( true ); + return true; } -SockAddrUnix::SockAddrUnix() : SockAddr( (struct sockaddr *)&mAddrUn ) -{ -} +bool zm::SockAddrUnix::resolve(const char *path, const char *proto) { + memset(&mAddrUn, 0, sizeof(mAddrUn)); -bool SockAddrUnix::resolve( const char *path, const char *proto ) -{ - memset( &mAddrUn, 0, sizeof(mAddrUn) ); - - strncpy( mAddrUn.sun_path, path, sizeof(mAddrUn.sun_path) ); + strncpy(mAddrUn.sun_path, path, sizeof(mAddrUn.sun_path)); + mAddrUn.sun_path[sizeof(mAddrUn.sun_path) - 1] = '\0'; mAddrUn.sun_family = AF_UNIX; - return( true ); + return true; } -bool Socket::socket() -{ - if ( mSd >= 0 ) - return( true ); +bool zm::Socket::socket() { + if (mSd >= 0) { + return true; + } - if ( (mSd = ::socket( getDomain(), getType(), 0 ) ) < 0 ) - { - Error( "socket(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if ((mSd = ::socket(getDomain(), getType(), 0)) < 0) { + Error("socket(), errno = %d, error = %s", errno, strerror(errno)); + return false; } int val = 1; - (void)::setsockopt( mSd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val) ); - (void)::setsockopt( mSd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val) ); + ::setsockopt(mSd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); + ::setsockopt(mSd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); mState = DISCONNECTED; - - return( true ); + return true; } -bool Socket::connect() -{ - if ( !socket() ) - return( false ); +bool zm::Socket::connect() { + if (!socket()) { + return false; + } - if ( ::connect( mSd, mRemoteAddr->getAddr(), getAddrSize() ) == -1 ) - { - Error( "connect(), errno = %d, error = %s", errno, strerror(errno) ); + if (::connect(mSd, mRemoteAddr->getAddr(), getAddrSize()) == -1) { + Error("connect(), errno = %d, error = %s", errno, strerror(errno)); close(); - return( false ); + return false; } mState = CONNECTED; - - return( true ); + return true; } -bool Socket::bind() -{ - if ( !socket() ) - return( false ); - - if ( ::bind( mSd, mLocalAddr->getAddr(), getAddrSize() ) == -1 ) - { - Error( "bind(), errno = %d, error = %s", errno, strerror(errno) ); - close(); - return( false ); +bool zm::Socket::bind() { + if (!socket()) { + return false; } - return( true ); + + if (::bind(mSd, mLocalAddr->getAddr(), getAddrSize()) == -1) { + Error("bind(), errno = %d, error = %s", errno, strerror(errno)); + close(); + return false; + } + return true; } -bool Socket::listen() -{ - if ( ::listen( mSd, SOMAXCONN ) == -1 ) - { - Error( "listen(), errno = %d, error = %s", errno, strerror(errno) ); +bool zm::Socket::listen() { + if (::listen(mSd, SOMAXCONN) == -1) { + Error("listen(), errno = %d, error = %s", errno, strerror(errno)); close(); - return( false ); + return false; } mState = LISTENING; - - return( true ); + return true; } -bool Socket::accept() -{ - struct sockaddr *rem_addr = mLocalAddr->getTempAddr(); +bool zm::Socket::accept() { + sockaddr rem_addr = {}; socklen_t rem_addr_size = getAddrSize(); int newSd = -1; - if ( (newSd = ::accept( mSd, rem_addr, &rem_addr_size )) == -1 ) - { - Error( "accept(), errno = %d, error = %s", errno, strerror(errno) ); + if ((newSd = ::accept(mSd, &rem_addr, &rem_addr_size)) == -1) { + Error("accept(), errno = %d, error = %s", errno, strerror(errno)); close(); - return( false ); + return false; } - ::close( mSd ); + ::close(mSd); mSd = newSd; mState = CONNECTED; - - return( true ); + return true; } -bool Socket::accept( int &newSd ) -{ - struct sockaddr *rem_addr = mLocalAddr->getTempAddr(); +bool zm::Socket::accept(int &newSd) { + sockaddr rem_addr = {}; socklen_t rem_addr_size = getAddrSize(); newSd = -1; - if ( (newSd = ::accept( mSd, rem_addr, &rem_addr_size )) == -1 ) - { - Error( "accept(), errno = %d, error = %s", errno, strerror(errno) ); + if ((newSd = ::accept(mSd, &rem_addr, &rem_addr_size)) == -1) { + Error("accept(), errno = %d, error = %s", errno, strerror(errno)); close(); - return( false ); + return false; } - return( true ); + return true; } -bool Socket::close() -{ - if ( mSd > -1 ) ::close( mSd ); +bool zm::Socket::close() { + if (mSd > -1) { + ::close(mSd); + } + mSd = -1; mState = CLOSED; - return( true ); + return true; } -int Socket::bytesToRead() const -{ +int zm::Socket::bytesToRead() const { int bytes_to_read = 0; - if ( ioctl( mSd, FIONREAD, &bytes_to_read ) < 0 ) - { - Error( "ioctl(), errno = %d, error = %s", errno, strerror(errno) ); - return( -1 ); + if (ioctl(mSd, FIONREAD, &bytes_to_read) < 0) { + Error("ioctl(), errno = %d, error = %s", errno, strerror(errno)); + return -1; } - return( bytes_to_read ); + return bytes_to_read; } -bool Socket::getBlocking( bool &blocking ) -{ +bool zm::Socket::getBlocking(bool &blocking) { int flags; - if ( (flags = fcntl( mSd, F_GETFL )) < 0 ) - { - Error( "fcntl(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if ((flags = fcntl(mSd, F_GETFL)) < 0) { + Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - blocking = (flags & O_NONBLOCK); - return( true ); + blocking = flags & O_NONBLOCK; + return true; } -bool Socket::setBlocking( bool blocking ) -{ -#if 0 - // ioctl is apparently not recommended - int ioctl_arg = !blocking; - if ( ioctl( mSd, FIONBIO, &ioctl_arg ) < 0 ) - { - Error( "ioctl(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); - } - return( true ); -#endif - +bool zm::Socket::setBlocking(bool blocking) { int flags; /* Now set it for non-blocking I/O */ - if ( (flags = fcntl( mSd, F_GETFL )) < 0 ) - { - Error( "fcntl(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if ((flags = fcntl(mSd, F_GETFL)) < 0) { + Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - if ( blocking ) - { + if (blocking) { flags &= ~O_NONBLOCK; - } - else - { + } else { flags |= O_NONBLOCK; } - if ( fcntl( mSd, F_SETFL, flags ) < 0 ) - { - Error( "fcntl(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if (fcntl(mSd, F_SETFL, flags) < 0) { + Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - return( true ); + return true; } -bool Socket::getSendBufferSize( int &buffersize ) const -{ +int zm::Socket::getSendBufferSize(int &buffersize) const { socklen_t optlen = sizeof(buffersize); - if ( getsockopt( mSd, SOL_SOCKET, SO_SNDBUF, &buffersize, &optlen ) < 0 ) - { - Error( "getsockopt(), errno = %d, error = %s", errno, strerror(errno) ); - return( -1 ); + if (getsockopt(mSd, SOL_SOCKET, SO_SNDBUF, &buffersize, &optlen) < 0) { + Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); + return -1; } - return( buffersize ); + return buffersize; } -bool Socket::getRecvBufferSize( int &buffersize ) const -{ +int zm::Socket::getRecvBufferSize(int &buffersize) const { socklen_t optlen = sizeof(buffersize); - if ( getsockopt( mSd, SOL_SOCKET, SO_RCVBUF, &buffersize, &optlen ) < 0 ) - { - Error( "getsockopt(), errno = %d, error = %s", errno, strerror(errno) ); - return( -1 ); + if (getsockopt(mSd, SOL_SOCKET, SO_RCVBUF, &buffersize, &optlen) < 0) { + Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); + return -1; } - return( buffersize ); + return buffersize; } -bool Socket::setSendBufferSize( int buffersize ) -{ - if ( setsockopt( mSd, SOL_SOCKET, SO_SNDBUF, (char *)&buffersize, sizeof(buffersize)) < 0 ) - { - Error( "setsockopt(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); +bool zm::Socket::setSendBufferSize(int buffersize) { + if (setsockopt(mSd, SOL_SOCKET, SO_SNDBUF, (char *) &buffersize, sizeof(buffersize)) < 0) { + Error("setsockopt(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - return( true ); + return true; } -bool Socket::setRecvBufferSize( int buffersize ) -{ - if ( setsockopt( mSd, SOL_SOCKET, SO_RCVBUF, (char *)&buffersize, sizeof(buffersize)) < 0 ) - { - Error( "setsockopt(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); +bool zm::Socket::setRecvBufferSize(int buffersize) { + if (setsockopt(mSd, SOL_SOCKET, SO_RCVBUF, (char *) &buffersize, sizeof(buffersize)) < 0) { + Error("setsockopt(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - return( true ); + return true; } -bool Socket::getRouting( bool &route ) const -{ +bool zm::Socket::getRouting(bool &route) const { int dontRoute; socklen_t optlen = sizeof(dontRoute); - if ( getsockopt( mSd, SOL_SOCKET, SO_DONTROUTE, &dontRoute, &optlen ) < 0 ) - { - Error( "getsockopt(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if (getsockopt(mSd, SOL_SOCKET, SO_DONTROUTE, &dontRoute, &optlen) < 0) { + Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); + return false; } route = !dontRoute; - return( true ); + return true; } -bool Socket::setRouting( bool route ) -{ +bool zm::Socket::setRouting(bool route) { int dontRoute = !route; - if ( setsockopt( mSd, SOL_SOCKET, SO_DONTROUTE, (char *)&dontRoute, sizeof(dontRoute)) < 0 ) - { - Error( "setsockopt(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if (setsockopt(mSd, SOL_SOCKET, SO_DONTROUTE, (char *) &dontRoute, sizeof(dontRoute)) < 0) { + Error("setsockopt(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - return( true ); + return true; } -bool Socket::getNoDelay( bool &nodelay ) const -{ +bool zm::Socket::getNoDelay(bool &nodelay) const { int int_nodelay; socklen_t optlen = sizeof(int_nodelay); - if ( getsockopt( mSd, IPPROTO_TCP, TCP_NODELAY, &int_nodelay, &optlen ) < 0 ) - { - Error( "getsockopt(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if (getsockopt(mSd, IPPROTO_TCP, TCP_NODELAY, &int_nodelay, &optlen) < 0) { + Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); + return false; } nodelay = int_nodelay; - return( true ); + return true; } -bool Socket::setNoDelay( bool nodelay ) -{ +bool zm::Socket::setNoDelay(bool nodelay) { int int_nodelay = nodelay; - if ( setsockopt( mSd, IPPROTO_TCP, TCP_NODELAY, (char *)&int_nodelay, sizeof(int_nodelay)) < 0 ) - { - Error( "setsockopt(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if (setsockopt(mSd, IPPROTO_TCP, TCP_NODELAY, (char *) &int_nodelay, sizeof(int_nodelay)) < 0) { + Error("setsockopt(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - return( true ); + return true; } -bool InetSocket::connect( const char *host, const char *serv ) -{ - struct addrinfo hints; - struct addrinfo *result, *rp; - int s; - char buf[255]; +bool zm::InetSocket::connect(const char *host, const char *serv) { + addrinfo hints; + addrinfo *result, *rp; + int s; + char buf[255]; + mAddressFamily = AF_UNSPEC; + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = getType(); + hints.ai_flags = 0; + hints.ai_protocol = 0; /* Any protocol */ + + s = getaddrinfo(host, serv, &hints, &result); + if (s != 0) { + Error("connect(): getaddrinfo: %s", gai_strerror(s)); + return false; + } + + /* getaddrinfo() returns a list of address structures. + * Try each address until we successfully connect(2). + * If socket(2) (or connect(2)) fails, we (close the socket + * and) try the next address. */ + + for (rp = result; rp != nullptr; rp = rp->ai_next) { + if (mSd != -1) { + if (::connect(mSd, rp->ai_addr, rp->ai_addrlen) != -1) { + break; /* Success */ + } + continue; + } + + memset(&buf, 0, sizeof(buf)); + if (rp->ai_family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in *) rp->ai_addr)->sin_addr, buf, sizeof(buf) - 1); + } else if (rp->ai_family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6 *) rp->ai_addr)->sin6_addr, buf, sizeof(buf) - 1); + } else { + strncpy(buf, "n/a", sizeof(buf) - 1); + } + + Debug(1, "connect(): Trying '%s', family '%d', proto '%d'", buf, rp->ai_family, rp->ai_protocol); + mSd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (mSd == -1) { + continue; + } + + int val = 1; + + ::setsockopt(mSd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); + ::setsockopt(mSd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); + mAddressFamily = rp->ai_family; /* save AF_ for ctrl and data connections */ + + if (::connect(mSd, rp->ai_addr, rp->ai_addrlen) != -1) { + break; /* Success */ + } + + ::close(mSd); + } + + freeaddrinfo(result); /* No longer needed */ + + if (rp == nullptr) { /* No address succeeded */ + Error("connect(), Could not connect"); mAddressFamily = AF_UNSPEC; - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ - hints.ai_socktype = getType(); - hints.ai_flags = 0; - hints.ai_protocol = 0; /* Any protocol */ + return false; + } - s = getaddrinfo(host, serv, &hints, &result); - if (s != 0) { - Error( "connect(): getaddrinfo: %s", gai_strerror(s) ); - return( false ); - } - - /* getaddrinfo() returns a list of address structures. - * Try each address until we successfully connect(2). - * If socket(2) (or connect(2)) fails, we (close the socket - * and) try the next address. */ - - for (rp = result; rp != NULL; rp = rp->ai_next) { - if (mSd != -1) { - if (::connect(mSd, rp->ai_addr, rp->ai_addrlen) != -1) - break; /* Success */ - continue; - } - memset(&buf, 0, sizeof(buf)); - if (rp->ai_family == AF_INET) { - inet_ntop(AF_INET, &((struct sockaddr_in *)rp->ai_addr)->sin_addr, buf, sizeof(buf)-1); - } - else if (rp->ai_family == AF_INET6) { - inet_ntop(AF_INET6, &((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr, buf, sizeof(buf)-1); - } - else { - strncpy(buf, "n/a", sizeof(buf)-1); - } - Debug( 1, "connect(): Trying '%s', family '%d', proto '%d'", buf, rp->ai_family, rp->ai_protocol); - mSd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); - if (mSd == -1) - continue; - - int val = 1; - - (void)::setsockopt( mSd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val) ); - (void)::setsockopt( mSd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val) ); - mAddressFamily = rp->ai_family; /* save AF_ for ctrl and data connections */ - - if (::connect(mSd, rp->ai_addr, rp->ai_addrlen) != -1) - break; /* Success */ - - ::close(mSd); - } - - freeaddrinfo(result); /* No longer needed */ - - if (rp == NULL) { /* No address succeeded */ - Error( "connect(), Could not connect" ); - mAddressFamily = AF_UNSPEC; - return( false ); - } - - mState = CONNECTED; - - return( true ); + mState = CONNECTED; + return true; } -bool InetSocket::connect( const char *host, int port ) -{ - char serv[8]; - snprintf(serv, sizeof(serv), "%d", port); +bool zm::InetSocket::connect(const char *host, int port) { + char serv[8]; + snprintf(serv, sizeof(serv), "%d", port); - return connect( host, serv ); + return connect(host, serv); } -bool InetSocket::bind( const char * host, const char * serv ) -{ - struct addrinfo hints; - struct addrinfo *result, *rp; - int s; - char buf[255]; +bool zm::InetSocket::bind(const char *host, const char *serv) { + addrinfo hints; - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ - hints.ai_socktype = getType(); - hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ - hints.ai_protocol = 0; /* Any protocol */ - hints.ai_canonname = NULL; - hints.ai_addr = NULL; - hints.ai_next = NULL; + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = getType(); + hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ + hints.ai_protocol = 0; /* Any protocol */ + hints.ai_canonname = nullptr; + hints.ai_addr = nullptr; + hints.ai_next = nullptr; - s = getaddrinfo(host, serv, &hints, &result); - if (s != 0) { - Error( "bind(): getaddrinfo: %s", gai_strerror(s) ); - return( false ); + addrinfo *result, *rp; + int s = getaddrinfo(host, serv, &hints, &result); + if (s != 0) { + Error("bind(): getaddrinfo: %s", gai_strerror(s)); + return false; + } + + char buf[255]; + /* getaddrinfo() returns a list of address structures. + * Try each address until we successfully bind(2). + * If socket(2) (or bind(2)) fails, we (close the socket + * and) try the next address. */ + for (rp = result; rp != nullptr; rp = rp->ai_next) { + memset(&buf, 0, sizeof(buf)); + if (rp->ai_family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in *) rp->ai_addr)->sin_addr, buf, sizeof(buf) - 1); + } else if (rp->ai_family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6 *) rp->ai_addr)->sin6_addr, buf, sizeof(buf) - 1); + } else { + strncpy(buf, "n/a", sizeof(buf) - 1); } - /* getaddrinfo() returns a list of address structures. - * Try each address until we successfully bind(2). - * If socket(2) (or bind(2)) fails, we (close the socket - * and) try the next address. */ - for (rp = result; rp != NULL; rp = rp->ai_next) { - memset(&buf, 0, sizeof(buf)); - if (rp->ai_family == AF_INET) { - inet_ntop(AF_INET, &((struct sockaddr_in *)rp->ai_addr)->sin_addr, buf, sizeof(buf)-1); - } - else if (rp->ai_family == AF_INET6) { - inet_ntop(AF_INET6, &((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr, buf, sizeof(buf)-1); - } - else { - strncpy(buf, "n/a", sizeof(buf)-1); - } - Debug( 1, "bind(): Trying '%s', family '%d', proto '%d'", buf, rp->ai_family, rp->ai_protocol); - mSd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); - if (mSd == -1) - continue; + Debug(1, "bind(): Trying '%s', family '%d', proto '%d'", buf, rp->ai_family, rp->ai_protocol); + mSd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (mSd == -1) { + continue; + } mState = DISCONNECTED; - if (::bind(mSd, rp->ai_addr, rp->ai_addrlen) == 0) - break; /* Success */ - - ::close(mSd); - mSd = -1; + if (::bind(mSd, rp->ai_addr, rp->ai_addrlen) == 0) { + break; /* Success */ } - if (rp == NULL) { /* No address succeeded */ - Error( "bind(), Could not bind" ); - return( false ); - } + ::close(mSd); + mSd = -1; + } - freeaddrinfo(result); /* No longer needed */ + if (rp == nullptr) { /* No address succeeded */ + Error("bind(), Could not bind"); + return false; + } - return( true ); + freeaddrinfo(result); /* No longer needed */ + return true; } -bool InetSocket::bind( const char * serv ) -{ - return bind( NULL, serv); +bool zm::InetSocket::bind(const char *serv) { + return bind(nullptr, serv); } -bool InetSocket::bind( const char * host, int port ) -{ - char serv[8]; - snprintf(serv, sizeof(serv), "%d", port); +bool zm::InetSocket::bind(const char *host, int port) { + char serv[8]; + snprintf(serv, sizeof(serv), "%d", port); - return bind( host, serv ); + return bind(host, serv); } -bool InetSocket::bind( int port ) -{ - char serv[8]; - snprintf(serv, sizeof(serv), "%d", port); +bool zm::InetSocket::bind(int port) { + char serv[8]; + snprintf(serv, sizeof(serv), "%d", port); - return bind( NULL, serv ); + return bind(nullptr, serv); } -bool TcpInetServer::listen() -{ - return( Socket::listen() ); +bool zm::TcpInetServer::listen() { + return Socket::listen(); } -bool TcpInetServer::accept() -{ - return( Socket::accept() ); +bool zm::TcpInetServer::accept() { + return Socket::accept(); } -bool TcpInetServer::accept( TcpInetSocket *&newSocket ) -{ +bool zm::TcpInetServer::accept(TcpInetSocket *&newSocket) { int newSd = -1; - newSocket = 0; + newSocket = nullptr; - if ( !Socket::accept( newSd ) ) - return( false ); + if (!Socket::accept(newSd)) { + return false; + } - newSocket = new TcpInetSocket( *this, newSd ); - - return( true ); + newSocket = new TcpInetSocket(*this, newSd); + return true; } -bool TcpUnixServer::accept( TcpUnixSocket *&newSocket ) -{ +bool zm::TcpUnixServer::accept(TcpUnixSocket *&newSocket) { int newSd = -1; - newSocket = 0; + newSocket = nullptr; - if ( !Socket::accept( newSd ) ) - return( false ); + if (!Socket::accept(newSd)) { + return false; + } - newSocket = new TcpUnixSocket( *this, newSd ); - - return( true ); + newSocket = new TcpUnixSocket(*this, newSd); + return true; } -Select::Select() : mHasTimeout( false ), mMaxFd( -1 ) -{ -} - -Select::Select( struct timeval timeout ) : mMaxFd( -1 ) -{ - setTimeout( timeout ); -} - -Select::Select( int timeout ) : mMaxFd( -1 ) -{ - setTimeout( timeout ); -} - -Select::Select( double timeout ) : mMaxFd( -1 ) -{ - setTimeout( timeout ); -} - -void Select::setTimeout( int timeout ) -{ - mTimeout.tv_sec = timeout; - mTimeout.tv_usec = 0; - mHasTimeout = true; -} - -void Select::setTimeout( double timeout ) -{ - mTimeout.tv_sec = int(timeout); - mTimeout.tv_usec = suseconds_t((timeout-mTimeout.tv_sec)*1000000.0); - mHasTimeout = true; -} - -void Select::setTimeout( struct timeval timeout ) -{ +void zm::Select::setTimeout(Microseconds timeout) { mTimeout = timeout; mHasTimeout = true; } -void Select::clearTimeout() -{ +void zm::Select::clearTimeout() { mHasTimeout = false; } -void Select::calcMaxFd() -{ +void zm::Select::calcMaxFd() { mMaxFd = -1; - for ( CommsSet::iterator iter = mReaders.begin(); iter != mReaders.end(); ++iter ) - if ( (*iter)->getMaxDesc() > mMaxFd ) + for (CommsSet::iterator iter = mReaders.begin(); iter != mReaders.end(); ++iter) { + if ((*iter)->getMaxDesc() > mMaxFd) mMaxFd = (*iter)->getMaxDesc(); - for ( CommsSet::iterator iter = mWriters.begin(); iter != mWriters.end(); ++iter ) - if ( (*iter)->getMaxDesc() > mMaxFd ) + } + for (CommsSet::iterator iter = mWriters.begin(); iter != mWriters.end(); ++iter) { + if ((*iter)->getMaxDesc() > mMaxFd) mMaxFd = (*iter)->getMaxDesc(); + } } -bool Select::addReader( CommsBase *comms ) -{ - if ( !comms->isOpen() ) - { - Error( "Unable to add closed reader" ); - return( false ); +bool zm::Select::addReader(CommsBase *comms) { + if (!comms->isOpen()) { + Error("Unable to add closed reader"); + return false; } - std::pair result = mReaders.insert( comms ); - if ( result.second ) - if ( comms->getMaxDesc() > mMaxFd ) + std::pair result = mReaders.insert(comms); + if (result.second) { + if (comms->getMaxDesc() > mMaxFd) { mMaxFd = comms->getMaxDesc(); - return( result.second ); + } + } + return result.second; } -bool Select::deleteReader( CommsBase *comms ) -{ - if ( !comms->isOpen() ) - { - Error( "Unable to add closed reader" ); - return( false ); +bool zm::Select::deleteReader(CommsBase *comms) { + if (!comms->isOpen()) { + Error("Unable to add closed reader"); + return false; } - if ( mReaders.erase( comms ) ) - { + if (mReaders.erase(comms)) { calcMaxFd(); - return( true ); + return true; } - return( false ); + return false; } -void Select::clearReaders() -{ +void zm::Select::clearReaders() { mReaders.clear(); mMaxFd = -1; } -bool Select::addWriter( CommsBase *comms ) -{ - std::pair result = mWriters.insert( comms ); - if ( result.second ) - if ( comms->getMaxDesc() > mMaxFd ) +bool zm::Select::addWriter(CommsBase *comms) { + std::pair result = mWriters.insert(comms); + if (result.second) { + if (comms->getMaxDesc() > mMaxFd) { mMaxFd = comms->getMaxDesc(); - return( result.second ); -} - -bool Select::deleteWriter( CommsBase *comms ) -{ - if ( mWriters.erase( comms ) ) - { - calcMaxFd(); - return( true ); + } } - return( false ); + return result.second; } -void Select::clearWriters() -{ +bool zm::Select::deleteWriter(CommsBase *comms) { + if (mWriters.erase(comms)) { + calcMaxFd(); + return true; + } + return false; +} + +void zm::Select::clearWriters() { mWriters.clear(); mMaxFd = -1; } -int Select::wait() -{ - struct timeval tempTimeout = mTimeout; - struct timeval *selectTimeout = mHasTimeout?&tempTimeout:NULL; +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 ) - FD_SET((*iter)->getReadDesc(),&rfds); + for (CommsSet::iterator iter = mReaders.begin(); iter != mReaders.end(); ++iter) { + FD_SET((*iter)->getReadDesc(), &rfds); + } mWriteable.clear(); FD_ZERO(&wfds); - for ( CommsSet::iterator iter = mWriters.begin(); iter != mWriters.end(); ++iter ) - FD_SET((*iter)->getWriteDesc(),&wfds); - - int nFound = select( mMaxFd+1, &rfds, &wfds, NULL, selectTimeout ); - if( nFound == 0 ) - { - Debug( 1, "Select timed out" ); + for (CommsSet::iterator iter = mWriters.begin(); iter != mWriters.end(); ++iter) { + FD_SET((*iter)->getWriteDesc(), &wfds); } - else if ( nFound < 0) - { - Error( "Select error: %s", strerror(errno) ); - } - else - { - for ( CommsSet::iterator iter = mReaders.begin(); iter != mReaders.end(); ++iter ) - if ( FD_ISSET((*iter)->getReadDesc(),&rfds) ) - mReadable.push_back( *iter ); - for ( CommsSet::iterator iter = mWriters.begin(); iter != mWriters.end(); ++iter ) - if ( FD_ISSET((*iter)->getWriteDesc(),&rfds) ) - mWriteable.push_back( *iter ); - } - return( nFound ); -} -const Select::CommsList &Select::getReadable() const -{ - return( mReadable ); -} - -const Select::CommsList &Select::getWriteable() const -{ - return( mWriteable ); + int nFound = select(mMaxFd + 1, &rfds, &wfds, nullptr, selectTimeout); + if (nFound == 0) { + Debug(1, "Select timed out"); + } else if (nFound < 0) { + Error("Select error: %s", strerror(errno)); + } else { + for (CommsSet::iterator iter = mReaders.begin(); iter != mReaders.end(); ++iter) { + if (FD_ISSET((*iter)->getReadDesc(), &rfds)) { + mReadable.push_back(*iter); + } + } + for (CommsSet::iterator iter = mWriters.begin(); iter != mWriters.end(); ++iter) { + if (FD_ISSET((*iter)->getWriteDesc(), &rfds)) { + mWriteable.push_back(*iter); + } + } + } + return nFound; } diff --git a/src/zm_comms.h b/src/zm_comms.h index e93951a85..ac772ae26 100644 --- a/src/zm_comms.h +++ b/src/zm_comms.h @@ -20,632 +20,578 @@ #ifndef ZM_COMMS_H #define ZM_COMMS_H -#include "zm_logger.h" #include "zm_exception.h" - -#include -#include +#include "zm_logger.h" +#include "zm_time.h" +#include #include -#include -#include - #include -#include #include +#include +#include +#include #if defined(BSD) #include #include #endif +namespace zm { + class CommsException : public Exception { -public: - explicit CommsException( const std::string &message ) : Exception( message ) { } + public: + explicit CommsException(const std::string &message) : Exception(message) {} }; class CommsBase { -protected: - const int &mRd; - const int &mWd; + protected: + CommsBase(const int &rd, const int &wd) : mRd(rd), mWd(wd) {} + virtual ~CommsBase() = default; -protected: - CommsBase( int &rd, int &wd ) : mRd( rd ), mWd( wd ) { - } - virtual ~CommsBase() { + public: + virtual bool close() = 0; + virtual bool isOpen() const = 0; + virtual bool isClosed() const = 0; + virtual bool setBlocking(bool blocking) = 0; + + public: + virtual int getReadDesc() const { return mRd; } + virtual int getWriteDesc() const { return mWd; } + int getMaxDesc() const { return mRd > mWd ? mRd : mWd; } + + virtual int read(void *msg, int len) { + ssize_t nBytes = ::read(mRd, msg, len); + if (nBytes < 0) { + Debug(1, "Read of %d bytes max on rd %d failed: %s", len, mRd, strerror(errno)); + } + return nBytes; } -public: - virtual bool close()=0; - virtual bool isOpen() const=0; - virtual bool isClosed() const=0; - virtual bool setBlocking( bool blocking )=0; - -public: - int getReadDesc() const { - return( mRd ); - } - int getWriteDesc() const { - return( mWd ); - } - int getMaxDesc() const { - return( mRd>mWd?mRd:mWd ); + virtual int write(const void *msg, int len) { + ssize_t nBytes = ::write(mWd, msg, len); + if (nBytes < 0) { + Debug(1, "Write of %d bytes on wd %d failed: %s", len, mWd, strerror(errno)); + } + return nBytes; } - virtual int read( void *msg, int len ) { - ssize_t nBytes = ::read( mRd, msg, len ); - if ( nBytes < 0 ) - Debug( 1, "Read of %d bytes max on rd %d failed: %s", len, mRd, strerror(errno) ); - return( nBytes ); + virtual int readV(const iovec *iov, int iovcnt) { + int nBytes = ::readv(mRd, iov, iovcnt); + if (nBytes < 0) { + Debug(1, "Readv of %d buffers max on rd %d failed: %s", iovcnt, mRd, strerror(errno)); + } + return nBytes; } - virtual int write( const void *msg, int len ) { - ssize_t nBytes = ::write( mWd, msg, len ); - if ( nBytes < 0 ) - Debug( 1, "Write of %d bytes on wd %d failed: %s", len, mWd, strerror(errno) ); - return( nBytes ); + + virtual int writeV(const iovec *iov, int iovcnt) { + ssize_t nBytes = ::writev(mWd, iov, iovcnt); + if (nBytes < 0) { + Debug(1, "Writev of %d buffers on wd %d failed: %s", iovcnt, mWd, strerror(errno)); + } + return nBytes; } - virtual int readV( const struct iovec *iov, int iovcnt ) { - int nBytes = ::readv( mRd, iov, iovcnt ); - if ( nBytes < 0 ) - Debug( 1, "Readv of %d buffers max on rd %d failed: %s", iovcnt, mRd, strerror(errno) ); - return( nBytes ); - } - virtual int writeV( const struct iovec *iov, int iovcnt ) { - ssize_t nBytes = ::writev( mWd, iov, iovcnt ); - if ( nBytes < 0 ) - Debug( 1, "Writev of %d buffers on wd %d failed: %s", iovcnt, mWd, strerror(errno) ); - return( nBytes ); - } - virtual int readV( int iovcnt, /* const void *msg1, int len1, */ ... ); - virtual int writeV( int iovcnt, /* const void *msg1, int len1, */ ... ); + + virtual int readV(int iovcnt, /* const void *msg1, int len1, */ ...); + virtual int writeV(int iovcnt, /* const void *msg1, int len1, */ ...); + + protected: + const int &mRd; + const int &mWd; }; class Pipe : public CommsBase { -protected: - int mFd[2]; - -public: - Pipe() : CommsBase( mFd[0], mFd[1] ) { + public: + Pipe() : CommsBase(mFd[0], mFd[1]) { mFd[0] = -1; mFd[1] = -1; } - ~Pipe() { - close(); - } -public: + ~Pipe() override { close(); } + bool open(); - bool close(); + bool close() override; - bool isOpen() const - { - return( mFd[0] != -1 && mFd[1] != -1 ); - } - int getReadDesc() const - { - return( mFd[0] ); - } - int getWriteDesc() const - { - return( mFd[1] ); - } + bool isOpen() const override { return mFd[0] != -1 && mFd[1] != -1; } + bool isClosed() const override { return !isOpen(); } + int getReadDesc() const override { return mFd[0]; } + int getWriteDesc() const override { return mFd[1]; } - bool setBlocking( bool blocking ); + bool setBlocking(bool blocking) override; + + protected: + int mFd[2]; }; class SockAddr { -private: - const struct sockaddr *mAddr; + public: + explicit SockAddr(const sockaddr *addr) : mAddr(addr) {} + virtual ~SockAddr() = default; -public: - explicit SockAddr( const struct sockaddr *addr ); - virtual ~SockAddr() { - } + static SockAddr *newSockAddr(const sockaddr &addr, socklen_t len); + static SockAddr *newSockAddr(const SockAddr *addr); - static SockAddr *newSockAddr( const struct sockaddr &addr, socklen_t len ); - static SockAddr *newSockAddr( const SockAddr *addr ); + int getDomain() const { return mAddr ? mAddr->sa_family : AF_UNSPEC; } + const sockaddr *getAddr() const { return mAddr; } - int getDomain() const { - return( mAddr?mAddr->sa_family:AF_UNSPEC ); - } + virtual socklen_t getAddrSize() const = 0; + virtual sockaddr *getTempAddr() const = 0; - const struct sockaddr *getAddr() const { - return( mAddr ); - } - virtual socklen_t getAddrSize() const=0; - virtual struct sockaddr *getTempAddr() const=0; + private: + const sockaddr *mAddr; }; class SockAddrInet : public SockAddr { -private: - struct sockaddr_in mAddrIn; - struct sockaddr_in mTempAddrIn; + public: + SockAddrInet() : SockAddr((sockaddr *) &mAddrIn) {} + explicit SockAddrInet(const SockAddrInet &addr) + : SockAddr((const sockaddr *) &mAddrIn), mAddrIn(addr.mAddrIn) {} + explicit SockAddrInet(const sockaddr_in *addr) + : SockAddr((const sockaddr *) &mAddrIn), mAddrIn(*addr) {} -public: - SockAddrInet(); - explicit SockAddrInet( const SockAddrInet &addr ) : SockAddr( (const struct sockaddr *)&mAddrIn ), mAddrIn( addr.mAddrIn ) { - } - explicit SockAddrInet( const struct sockaddr_in *addr ) : SockAddr( (const struct sockaddr *)&mAddrIn ), mAddrIn( *addr ) { - } + bool resolve(const char *host, const char *serv, const char *proto); + bool resolve(const char *host, int port, const char *proto); + bool resolve(const char *serv, const char *proto); + bool resolve(int port, const char *proto); + socklen_t getAddrSize() const override { return sizeof(mAddrIn); } + sockaddr *getTempAddr() const override { return (sockaddr *) &mTempAddrIn; } - bool resolve( const char *host, const char *serv, const char *proto ); - bool resolve( const char *host, int port, const char *proto ); - bool resolve( const char *serv, const char *proto ); - bool resolve( int port, const char *proto ); + static socklen_t addrSize() { return sizeof(sockaddr_in); } - socklen_t getAddrSize() const { - return( sizeof(mAddrIn) ); - } - struct sockaddr *getTempAddr() const { - return( (sockaddr *)&mTempAddrIn ); - } - -public: - static socklen_t addrSize() { - return( sizeof(sockaddr_in) ); - } + private: + sockaddr_in mAddrIn; + sockaddr_in mTempAddrIn; }; class SockAddrUnix : public SockAddr { -private: - struct sockaddr_un mAddrUn; - struct sockaddr_un mTempAddrUn; + public: + SockAddrUnix() : SockAddr((sockaddr *) &mAddrUn) {} + SockAddrUnix(const SockAddrUnix &addr) + : SockAddr((const sockaddr *) &mAddrUn), mAddrUn(addr.mAddrUn) {} + explicit SockAddrUnix(const sockaddr_un *addr) + : SockAddr((const sockaddr *) &mAddrUn), mAddrUn(*addr) {} -public: - SockAddrUnix(); - SockAddrUnix( const SockAddrUnix &addr ) : SockAddr( (const struct sockaddr *)&mAddrUn ), mAddrUn( addr.mAddrUn ) { - } - explicit SockAddrUnix( const struct sockaddr_un *addr ) : SockAddr( (const struct sockaddr *)&mAddrUn ), mAddrUn( *addr ) { - } + bool resolve(const char *path, const char *proto); - bool resolve( const char *path, const char *proto ); + socklen_t getAddrSize() const override { return sizeof(mAddrUn); } + sockaddr *getTempAddr() const override { return (sockaddr *) &mTempAddrUn; } - socklen_t getAddrSize() const { - return( sizeof(mAddrUn) ); - } - struct sockaddr *getTempAddr() const { - return( (sockaddr *)&mTempAddrUn ); - } + static socklen_t addrSize() { return sizeof(sockaddr_un); } -public: - static socklen_t addrSize() { - return( sizeof(sockaddr_un) ); - } + private: + sockaddr_un mAddrUn; + sockaddr_un mTempAddrUn; }; class Socket : public CommsBase { -protected: - typedef enum { CLOSED, DISCONNECTED, LISTENING, CONNECTED } State; + protected: + enum State { CLOSED, DISCONNECTED, LISTENING, CONNECTED }; -protected: - int mSd; - State mState; - SockAddr *mLocalAddr; - SockAddr *mRemoteAddr; + Socket() : CommsBase(mSd, mSd), + mSd(-1), + mState(CLOSED), + mLocalAddr(nullptr), + mRemoteAddr(nullptr) {} + Socket(const Socket &socket, int newSd) : CommsBase(mSd, mSd), + mSd(newSd), + mState(CONNECTED), + mLocalAddr(nullptr), + mRemoteAddr(nullptr) { + if (socket.mLocalAddr) + mLocalAddr = SockAddr::newSockAddr(mLocalAddr); + if (socket.mRemoteAddr) + mRemoteAddr = SockAddr::newSockAddr(mRemoteAddr); + } -protected: - Socket() : CommsBase( mSd, mSd ), mSd( -1 ), mState( CLOSED ), mLocalAddr( 0 ), mRemoteAddr( 0 ) { - } - Socket( const Socket &socket, int newSd ) : CommsBase( mSd, mSd ), mSd( newSd ), mState( CONNECTED ), mLocalAddr( 0 ), mRemoteAddr( 0 ) { - if ( socket.mLocalAddr ) - mLocalAddr = SockAddr::newSockAddr( mLocalAddr ); - if ( socket.mRemoteAddr ) - mRemoteAddr = SockAddr::newSockAddr( mRemoteAddr ); - } virtual ~Socket() { close(); delete mLocalAddr; delete mRemoteAddr; } -public: - bool isOpen() const { - return( !isClosed() ); - } - bool isClosed() const { - return( mState == CLOSED ); - } - bool isDisconnected() const { - return( mState == DISCONNECTED ); - } - bool isConnected() const { - return( mState == CONNECTED ); - } - virtual bool close(); + public: + bool isOpen() const override { return !isClosed(); } + bool isClosed() const override { return mState == CLOSED; } + bool isDisconnected() const { return mState == DISCONNECTED; } + bool isConnected() const { return mState == CONNECTED; } + bool close() override; -protected: - bool isListening() const { - return( mState == LISTENING ); + virtual int send(const void *msg, int len) const { + ssize_t nBytes = ::send(mSd, msg, len, 0); + if (nBytes < 0) { + Debug(1, "Send of %d bytes on sd %d failed: %s", len, mSd, strerror(errno)); + } + return nBytes; } -protected: - virtual bool socket(); - virtual bool bind(); + virtual int recv(void *msg, int len) const { + ssize_t nBytes = ::recv(mSd, msg, len, 0); + if (nBytes < 0) { + Debug(1, "Recv of %d bytes max on sd %d failed: %s", len, mSd, strerror(errno)); + } + return nBytes; + } -protected: - virtual bool connect(); - virtual bool listen(); - virtual bool accept(); - virtual bool accept( int & ); + virtual int send(const std::string &msg) const { + ssize_t nBytes = ::send(mSd, msg.data(), msg.size(), 0); + if (nBytes < 0) { + Debug(1, "Send of string '%s' (%zd bytes) on sd %d failed: %s", + msg.c_str(), + msg.size(), + mSd, + strerror(errno)); + } + return nBytes; + } -public: - virtual int send( const void *msg, int len ) const { - ssize_t nBytes = ::send( mSd, msg, len, 0 ); - if ( nBytes < 0 ) - Debug( 1, "Send of %d bytes on sd %d failed: %s", len, mSd, strerror(errno) ); - return( nBytes ); - } - virtual int recv( void *msg, int len ) const { - ssize_t nBytes = ::recv( mSd, msg, len, 0 ); - if ( nBytes < 0 ) - Debug( 1, "Recv of %d bytes max on sd %d failed: %s", len, mSd, strerror(errno) ); - return( nBytes ); - } - virtual int send( const std::string &msg ) const { - ssize_t nBytes = ::send( mSd, msg.data(), msg.size(), 0 ); - if ( nBytes < 0 ) - Debug( 1, "Send of string '%s' (%zd bytes) on sd %d failed: %s", msg.c_str(), msg.size(), mSd, strerror(errno) ); - return( nBytes ); - } - virtual int recv( std::string &msg ) const { - 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 { + msg.reserve(ZM_NETWORK_BUFSIZ); + 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=0 ) const { - ssize_t nBytes = ::sendto( mSd, msg, len, 0, addr?addr->getAddr():NULL, 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, NULL, 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; }; +} + #endif // ZM_COMMS_H diff --git a/src/zm_config.cpp b/src/zm_config.cpp index 7d75042ba..e2f0e53f0 100644 --- a/src/zm_config.cpp +++ b/src/zm_config.cpp @@ -15,105 +15,104 @@ // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// +// + +#include "zm_config.h" -#include "zm.h" #include "zm_db.h" - -#include -#include -#include -#include +#include "zm_logger.h" +#include "zm_utils.h" +#include +#include #include #include -#include "zm_utils.h" - // Note that Error and Debug calls won't actually go anywhere unless you // set the relevant ENV vars because the logger gets it's setting from the // config. -void zmLoadConfig() { - +void zmLoadStaticConfig() { // Process name, value pairs from the main config file first - char configFile[PATH_MAX] = ZM_CONFIG; - process_configfile(configFile); + process_configfile(ZM_CONFIG); // Search for user created config files. If one or more are found then // update the Config hash with those values - DIR* configSubFolder = opendir(ZM_CONFIG_SUBDIR); - if ( configSubFolder ) { // subfolder exists and is readable - char glob_pattern[PATH_MAX] = ""; - snprintf(glob_pattern, sizeof(glob_pattern), "%s/*.conf", ZM_CONFIG_SUBDIR); + DIR *configSubFolder = opendir(ZM_CONFIG_SUBDIR); + if (configSubFolder) { // subfolder exists and is readable + std::string glob_pattern = stringtf("%s/*.conf", ZM_CONFIG_SUBDIR); glob_t pglob; - int glob_status = glob(glob_pattern, 0, 0, &pglob); - if ( glob_status != 0 ) { - if ( glob_status < 0 ) { - Error("Can't glob '%s': %s", glob_pattern, strerror(errno)); + 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.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++ ) { + for (unsigned int i = 0; i < pglob.gl_pathc; i++) { process_configfile(pglob.gl_pathv[i]); } } globfree(&pglob); closedir(configSubFolder); } +} - zmDbConnect(); +void zmLoadDBConfig() { + if (!zmDbConnected) { + Fatal("Not connected to the database. Can't continue."); + } config.Load(); config.Assign(); // Populate the server config entries - if ( !staticConfig.SERVER_ID ) { - if ( !staticConfig.SERVER_NAME.empty() ) { + if (!staticConfig.SERVER_ID) { + if (!staticConfig.SERVER_NAME.empty()) { Debug(1, "Fetching ZM_SERVER_ID For Name = %s", staticConfig.SERVER_NAME.c_str()); std::string sql = stringtf("SELECT `Id` FROM `Servers` WHERE `Name`='%s'", - staticConfig.SERVER_NAME.c_str()); + staticConfig.SERVER_NAME.c_str()); zmDbRow dbrow; - if ( dbrow.fetch(sql.c_str()) ) { + if (dbrow.fetch(sql)) { staticConfig.SERVER_ID = atoi(dbrow[0]); } else { Fatal("Can't get ServerId for Server %s", staticConfig.SERVER_NAME.c_str()); } } // end if has SERVER_NAME - } else if ( staticConfig.SERVER_NAME.empty() ) { + } else if (staticConfig.SERVER_NAME.empty()) { Debug(1, "Fetching ZM_SERVER_NAME For Id = %d", staticConfig.SERVER_ID); std::string sql = stringtf("SELECT `Name` FROM `Servers` WHERE `Id`='%d'", staticConfig.SERVER_ID); - + zmDbRow dbrow; - if ( dbrow.fetch(sql.c_str()) ) { + if (dbrow.fetch(sql)) { staticConfig.SERVER_NAME = std::string(dbrow[0]); } else { Fatal("Can't get ServerName for Server ID %d", staticConfig.SERVER_ID); } - if ( staticConfig.SERVER_ID ) { - Debug(3, "Multi-server configuration detected. Server is %d.", staticConfig.SERVER_ID); + if (staticConfig.SERVER_ID) { + Debug(3, "Multi-server configuration detected. Server is %d.", staticConfig.SERVER_ID); } else { - Debug(3, "Single server configuration assumed because no Server ID or Name was specified."); + Debug(3, "Single server configuration assumed because no Server ID or Name was specified."); } } - snprintf(staticConfig.capture_file_format, sizeof(staticConfig.capture_file_format), "%%s/%%0%dd-capture.jpg", config.event_image_digits); - snprintf(staticConfig.analyse_file_format, sizeof(staticConfig.analyse_file_format), "%%s/%%0%dd-analyse.jpg", config.event_image_digits); - snprintf(staticConfig.general_file_format, sizeof(staticConfig.general_file_format), "%%s/%%0%dd-%%s", config.event_image_digits); - snprintf(staticConfig.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* configFile) { +void process_configfile(char const *configFile) { FILE *cfg; char line[512]; - if ( (cfg = fopen(configFile, "r")) == NULL ) { + if ( (cfg = fopen(configFile, "r")) == nullptr ) { Fatal("Can't open %s: %s", configFile, strerror(errno)); return; } - while ( fgets(line, sizeof(line), cfg) != NULL ) { + while ( fgets(line, sizeof(line), cfg) != nullptr ) { char *line_ptr = line; // Trim off any cr/lf line endings @@ -258,16 +257,16 @@ ConfigItem::~ConfigItem() { void ConfigItem::ConvertValue() const { if ( !strcmp( type, "boolean" ) ) { cfg_type = CFG_BOOLEAN; - cfg_value.boolean_value = (bool)strtol(value, 0, 0); + cfg_value.boolean_value = (bool)strtol(value, nullptr, 0); } else if ( !strcmp(type, "integer") ) { cfg_type = CFG_INTEGER; - cfg_value.integer_value = strtol(value, 0, 10); + cfg_value.integer_value = strtol(value, nullptr, 10); } else if ( !strcmp(type, "hexadecimal") ) { cfg_type = CFG_INTEGER; - cfg_value.integer_value = strtol(value, 0, 16); + cfg_value.integer_value = strtol(value, nullptr, 16); } else if ( !strcmp(type, "decimal") ) { cfg_type = CFG_DECIMAL; - cfg_value.decimal_value = strtod(value, 0); + cfg_value.decimal_value = strtod(value, nullptr); } else { cfg_type = CFG_STRING; cfg_value.string_value = value; @@ -323,36 +322,25 @@ const char *ConfigItem::StringValue() const { return cfg_value.string_value; } -Config::Config() { - n_items = 0; - items = 0; -} +Config::Config() : n_items(0), items(nullptr) { } Config::~Config() { if ( items ) { for ( int i = 0; i < n_items; i++ ) { delete items[i]; - items[i] = NULL; + items[i] = nullptr; } delete[] items; - items = NULL; + items = nullptr; } } void Config::Load() { - static char sql[ZM_SQL_SML_BUFSIZ]; - - strncpy(sql, "SELECT `Name`, `Value`, `Type` FROM `Config` ORDER BY `Id`", sizeof(sql) ); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); + MYSQL_RES *result = zmDbFetch("SELECT `Name`, `Value`, `Type` FROM `Config` ORDER BY `Id`"); + if (!result) { + exit(-1); } - MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } n_items = mysql_num_rows(result); if ( n_items <= ZM_MAX_CFG_ID ) { @@ -361,7 +349,7 @@ void Config::Load() { } items = new ConfigItem *[n_items]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { + for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { items[i] = new ConfigItem(dbrow[0], dbrow[1], dbrow[2]); } mysql_free_result(result); diff --git a/src/zm_config.h.in b/src/zm_config.h similarity index 76% rename from src/zm_config.h.in rename to src/zm_config.h index 9831677f0..e2ec9781a 100644 --- a/src/zm_config.h.in +++ b/src/zm_config.h @@ -1,45 +1,30 @@ // // ZoneMinder Configuration, $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_CONFIG_H #define ZM_CONFIG_H -#if !defined(PATH_MAX) -#define PATH_MAX 1024 -#endif #include "config.h" +#include "zm_config_data.h" #include "zm_config_defines.h" - #include -#define ZM_CONFIG "@ZM_CONFIG@" // Path to config file -#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@ - -#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 @@ -49,20 +34,16 @@ #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 #define ZM_SAMPLE_RATE int(1000000/ZM_MAX_FPS) // A general nyquist sample frequency for delays etc #define ZM_SUSPENDED_RATE int(1000000/4) // A slower rate for when disabled etc -extern void zmLoadConfig(); +void zmLoadStaticConfig(); +void zmLoadDBConfig(); -extern void process_configfile( char* configFile ); +extern void process_configfile(char const *configFile); struct StaticConfig { std::string DB_HOST; @@ -84,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; @@ -118,8 +99,9 @@ public: double DecimalValue() const; const char *StringValue() const; - ConfigItem &operator=(const ConfigItem item) { - Copy(item);return *this; + ConfigItem &operator=(const ConfigItem &item) { + Copy(item); + return *this; } inline operator bool() const { return BooleanValue(); diff --git a/src/zm_box.cpp b/src/zm_config_data.h.in similarity index 69% rename from src/zm_box.cpp rename to src/zm_config_data.h.in index 4a69fbf6d..f16135e92 100644 --- a/src/zm_box.cpp +++ b/src/zm_config_data.h.in @@ -1,7 +1,7 @@ // -// ZoneMinder Box Class Implementation, $Date$, $Revision$ +// ZoneMinder Configuration, $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 @@ -17,7 +17,11 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" -#include "zm_box.h" +#ifndef ZM_CONFIG_DATA_H +#define ZM_CONFIG_DATA_H -// This section deliberately left blank +#define ZM_CONFIG "@ZM_CONFIG@" // Path to config file +#define ZM_CONFIG_SUBDIR "@ZM_CONFIG_SUBDIR@" // Path to the ZoneMinder config subfolder +#define ZM_VERSION "@VERSION@" // ZoneMinder Version + +#endif // ZM_CONFIG_DATA_H diff --git a/src/zm_coord.h b/src/zm_coord.h deleted file mode 100644 index 9bf31144c..000000000 --- a/src/zm_coord.h +++ /dev/null @@ -1,66 +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.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() { return( x ); } - inline const int &X() const { return( x ); } - inline int &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 ) { return( x == coord.x && y == coord.y ); } - inline bool operator!=( const Coord &coord ) { return( x != coord.x || y != coord.y ); } - inline bool operator>( const Coord &coord ) { return( x > coord.x && y > coord.y ); } - inline bool operator>=( const Coord &coord ) { return( !(operator<(coord)) ); } - inline bool operator<( const Coord &coord ) { return( x < coord.x && y < coord.y ); } - inline bool operator<=( const Coord &coord ) { return( !(operator>(coord)) ); } - inline 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 6b78e169b..5f4139b61 100644 --- a/src/zm_crypt.cpp +++ b/src/zm_crypt.cpp @@ -1,12 +1,79 @@ -#include "zm.h" #include "zm_crypt.h" + +#include "zm_logger.h" +#include "zm_utils.h" #include "BCrypt.hpp" -#include "jwt.h" #include -#include -#include +#include + +#if HAVE_LIBJWT +#include +#else +#include +#endif // returns username if valid, "" if not +#if HAVE_LIBJWT +std::pair verifyToken(std::string jwt_token_str, std::string key) { + std::string username = ""; + unsigned int token_issued_at = 0; + int err = 0; + jwt_t *jwt = nullptr; + + err = jwt_new(&jwt); + 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) { + jwt_free(jwt); + Error("Error setting Algorithm for JWT decode"); + return std::make_pair("", 0); + } + + 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) { + jwt_free(jwt); + Error("Missing token type. This should not happen"); + return std::make_pair("", 0); + } 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) { + jwt_free(jwt); + Error("User not found in claim"); + return std::make_pair("", 0); + } + + username = std::string(c_username); + 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) { + jwt_free(jwt); + Error("IAT not found in claim. This should not happen"); + return std::make_pair("", 0); + } + + Debug(1, "Got IAT token=%u", token_issued_at); + jwt_free(jwt); + return std::make_pair(username, token_issued_at); +} +#else // HAVE_LIBJWT std::pair verifyToken(std::string jwt_token_str, std::string key) { std::string username = ""; unsigned int token_issued_at = 0; @@ -21,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 { @@ -48,18 +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 @@ -68,31 +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); - - SHA_CTX ctx1, ctx2; - unsigned char digest_interim[SHA_DIGEST_LENGTH]; - unsigned char digest_final[SHA_DIGEST_LENGTH]; + Debug(1, "%s is using an SHA1 encoded password", username); - //get first iteration - SHA1_Init(&ctx1); - SHA1_Update(&ctx1, input_password, strlen(input_password)); - SHA1_Final(digest_interim, &ctx1); + SHA1::Digest digest = SHA1::GetDigestOf(SHA1::GetDigestOf(input_password)); + std::string hex_digest = '*' + StringToUpper(ByteArrayToHexString(digest)); - //2nd iteration - SHA1_Init(&ctx2); - SHA1_Update(&ctx2, digest_interim,SHA_DIGEST_LENGTH); - SHA1_Final (digest_final, &ctx2); - - 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] == '$') && @@ -103,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 340abc36c..b495dd536 100644 --- a/src/zm_crypt.h +++ b/src/zm_crypt.h @@ -20,12 +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); +std::pair verifyToken(std::string token, std::string key); +namespace zm { +namespace crypto { +namespace impl { -bool verifyPassword( const char *username, const char *input_password, const char *db_password_hash); +#if defined(HAVE_LIBGNUTLS) +template +using Hash = gnutls::GenericHashImpl; +#elif defined(HAVE_LIBOPENSSL) +template +using Hash = openssl::GenericHashImpl; +#endif +} +} +} -std::pair verifyToken(std::string token, std::string key); -#endif // ZM_CRYPT_H \ No newline at end of file +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 1f3a6a54b..27a213ed1 100644 --- a/src/zm_curl_camera.cpp +++ b/src/zm_curl_camera.cpp @@ -1,30 +1,41 @@ // // 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. -// - -#include "zm.h" +// #include "zm_curl_camera.h" -#include "zm_packetqueue.h" +#include "zm_packet.h" +#include #if HAVE_LIBCURL +/* Func ptrs for libcurl functions */ +static void *curl_lib = nullptr; +static CURLcode (*curl_global_init_f)(long) = nullptr; +static void (*curl_global_cleanup_f)(void) = nullptr; +static const char* (*curl_easy_strerror_f)(CURLcode) = nullptr; +static char* (*curl_version_f)(void) = nullptr; +static CURL* (*curl_easy_init_f)(void) = nullptr; +static CURLcode (*curl_easy_getinfo_f)(CURL* , CURLINFO, ...) = nullptr; +static CURLcode (*curl_easy_perform_f)(CURL*) = nullptr; +static CURLcode (*curl_easy_setopt_f)(CURL*, CURLoption, ...) = nullptr; +static void (*curl_easy_cleanup_f)(CURL*) = nullptr; + #define CURL_MAXRETRY 5 #define CURL_BUFFER_INITIAL_SIZE 65536 @@ -33,19 +44,64 @@ const char* content_type_match = "Content-Type:"; size_t content_length_match_len; size_t content_type_match_len; -cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, unsigned int p_width, unsigned int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : - Camera( p_id, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), - mPath( p_path ), mUser( p_user ), mPass ( p_pass ), bTerminate( false ), bReset( false ), mode ( MODE_UNSET ) -{ +void bind_libcurl_symbols() { - if ( capture ) { + if (curl_lib) + return; + + curl_lib = dlopen("libcurl.so", RTLD_LAZY | RTLD_GLOBAL); + if (!curl_lib) { + 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 + *(void**) (&curl_global_init_f) = dlsym(curl_lib, "curl_global_init"); + *(void**) (&curl_global_cleanup_f) = dlsym(curl_lib, "curl_global_cleanup"); + *(void**) (&curl_easy_strerror_f) = dlsym(curl_lib, "curl_easy_strerror"); + *(void**) (&curl_version_f) = dlsym(curl_lib, "curl_version"); + *(void**) (&curl_easy_init_f) = dlsym(curl_lib, "curl_easy_init"); + *(void**) (&curl_easy_getinfo_f) = dlsym(curl_lib, "curl_easy_getinfo"); + *(void**) (&curl_easy_perform_f) = dlsym(curl_lib, "curl_easy_perform"); + *(void**) (&curl_easy_setopt_f) = dlsym(curl_lib, "curl_easy_setopt"); + *(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) +{ + if (capture) { Initialise(); } } cURLCamera::~cURLCamera() { - if ( capture ) { - + if (capture) { Terminate(); } } @@ -56,34 +112,41 @@ void cURLCamera::Initialise() { databuffer.expand(CURL_BUFFER_INITIAL_SIZE); + bind_libcurl_symbols(); /* cURL initialization */ - CURLcode cRet = curl_global_init(CURL_GLOBAL_ALL); - if(cRet != CURLE_OK) { - Fatal("libcurl initialization failed: ", curl_easy_strerror(cRet)); + CURLcode cRet = (*curl_global_init_f)(CURL_GLOBAL_ALL); + 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()); + Debug(2, "libcurl version: %s", (*curl_version_f)()); /* Create the shared data mutex */ - int nRet = pthread_mutex_init(&shareddata_mutex, NULL); - if(nRet != 0) { - Fatal("Shared data mutex creation failed: %s",strerror(nRet)); + int nRet = pthread_mutex_init(&shareddata_mutex, nullptr); + 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, NULL); - if(nRet != 0) { - Fatal("Data available condition variable creation failed: %s",strerror(nRet)); + nRet = pthread_cond_init(&data_available_cond, nullptr); + 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, NULL); - if(nRet != 0) { - Fatal("Request complete condition variable creation failed: %s",strerror(nRet)); + nRet = pthread_cond_init(&request_complete_cond, nullptr); + if (nRet != 0) { + Error("Request complete condition variable creation failed: %s",strerror(nRet)); + return; } /* Create the thread */ - nRet = pthread_create(&thread, NULL, thread_func_dispatcher, this); - if(nRet != 0) { - Fatal("Thread creation failed: %s",strerror(nRet)); + nRet = pthread_create(&thread, nullptr, thread_func_dispatcher, this); + if (nRet != 0) { + Error("Thread creation failed: %s",strerror(nRet)); + return; } } @@ -92,7 +155,7 @@ void cURLCamera::Terminate() { bTerminate = true; /* Wait for thread termination */ - pthread_join(thread, NULL); + pthread_join(thread, nullptr); /* Destroy condition variables */ pthread_cond_destroy(&request_complete_cond); @@ -102,21 +165,24 @@ void cURLCamera::Terminate() { pthread_mutex_destroy(&shareddata_mutex); /* cURL cleanup */ - curl_global_cleanup(); + (*curl_global_cleanup_f)(); + 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( Image &image ) { +int cURLCamera::Capture(std::shared_ptr &zm_packet) { bool frameComplete = false; /* MODE_STREAM specific variables */ @@ -129,10 +195,9 @@ int cURLCamera::Capture( Image &image ) { /* 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(); @@ -140,40 +205,40 @@ int cURLCamera::Capture( Image &image ) { 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 -20; + 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; @@ -184,18 +249,22 @@ int cURLCamera::Capture( Image &image ) { 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; } @@ -206,69 +275,85 @@ int cURLCamera::Capture( Image &image ) { /* 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 */ - 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 -18; + 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 */ - 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 { @@ -282,12 +367,13 @@ int cURLCamera::Capture( Image &image ) { nRet = pthread_cond_wait(&request_complete_cond,&shareddata_mutex); if(nRet != 0) { Error("Failed waiting for request complete condition variable: %s",strerror(nRet)); - return -19; + return -1; } } } else { /* Failed to match content-type */ - Fatal("Unable to match Content-Type. Check URL, username and password"); + Error("Unable to match Content-Type. Check URL, username and password"); + return -21; } /* mode */ } /* frameComplete loop */ @@ -295,21 +381,15 @@ int cURLCamera::Capture( Image &image ) { /* Release the mutex */ unlock(); - if(!frameComplete) - return -1; + if (!frameComplete) + return 0; return 1; } int cURLCamera::PostCapture() { - // Nothing to do here - return( 0 ); -} - -int cURLCamera::CaptureAndRecord( Image &image, struct timeval recording, char* event_directory ) { - Error("Capture and Record not implemented for the cURL camera type"); // Nothing to do here - return( 0 ); + return 1; } size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) { @@ -320,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; @@ -335,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(':'); } @@ -357,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; } @@ -376,90 +456,122 @@ void* cURLCamera::thread_func() { long tRet; double dSize; - c = curl_easy_init(); - if(c == NULL) { - Fatal("Failed getting easy handle from libcurl"); + c = (*curl_easy_init_f)(); + if (c == nullptr) { + dlclose(curl_lib); + Error("Failed getting easy handle from libcurl"); + tRet = -51; + return (void*)tRet; } CURLcode cRet; /* Set URL */ - cRet = curl_easy_setopt(c, CURLOPT_URL, mPath.c_str()); - if(cRet != CURLE_OK) - Fatal("Failed setting libcurl URL: %s", curl_easy_strerror(cRet)); - - /* Header callback */ - cRet = curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, &header_callback_dispatcher); - if(cRet != CURLE_OK) - Fatal("Failed setting libcurl header callback function: %s", curl_easy_strerror(cRet)); - cRet = curl_easy_setopt(c, CURLOPT_HEADERDATA, this); - if(cRet != CURLE_OK) - Fatal("Failed setting libcurl header callback object: %s", curl_easy_strerror(cRet)); - - /* Data callback */ - cRet = curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, &data_callback_dispatcher); - if(cRet != CURLE_OK) - Fatal("Failed setting libcurl data callback function: %s", curl_easy_strerror(cRet)); - cRet = curl_easy_setopt(c, CURLOPT_WRITEDATA, this); - if(cRet != CURLE_OK) - Fatal("Failed setting libcurl data callback object: %s", curl_easy_strerror(cRet)); - - /* Progress callback */ - cRet = curl_easy_setopt(c, CURLOPT_NOPROGRESS, 0); - if(cRet != CURLE_OK) - Fatal("Failed enabling libcurl progress callback function: %s", curl_easy_strerror(cRet)); - cRet = curl_easy_setopt(c, CURLOPT_PROGRESSFUNCTION, &progress_callback_dispatcher); - if(cRet != CURLE_OK) - Fatal("Failed setting libcurl progress callback function: %s", curl_easy_strerror(cRet)); - cRet = curl_easy_setopt(c, CURLOPT_PROGRESSDATA, this); - if(cRet != CURLE_OK) - Fatal("Failed setting libcurl progress callback object: %s", curl_easy_strerror(cRet)); - - /* Set username and password */ - if(!mUser.empty()) { - cRet = curl_easy_setopt(c, CURLOPT_USERNAME, mUser.c_str()); - if(cRet != CURLE_OK) - Error("Failed setting username: %s", curl_easy_strerror(cRet)); + 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)); + tRet = -52; + return (void*)tRet; } - if(!mPass.empty()) { - cRet = curl_easy_setopt(c, CURLOPT_PASSWORD, mPass.c_str()); - if(cRet != CURLE_OK) - Error("Failed setting password: %s", curl_easy_strerror(cRet)); + + /* Header callback */ + cRet = (*curl_easy_setopt_f)(c, CURLOPT_HEADERFUNCTION, &header_callback_dispatcher); + 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) { + 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) { + 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) { + 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)); + tRet = -57; + return (void*)tRet; + } + + cRet = (*curl_easy_setopt_f)(c, CURLOPT_PROGRESSFUNCTION, &progress_callback_dispatcher); + 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) { + 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()) { + cRet = (*curl_easy_setopt_f)(c, CURLOPT_USERNAME, mUser.c_str()); + if (cRet != CURLE_OK) + Error("Failed setting username: %s", (*curl_easy_strerror_f)(cRet)); + } + if (!mPass.empty()) { + cRet = (*curl_easy_setopt_f)(c, CURLOPT_PASSWORD, mPass.c_str()); + if (cRet != CURLE_OK) + Error("Failed setting password: %s", (*curl_easy_strerror_f)(cRet)); } /* Authenication preference */ - cRet = curl_easy_setopt(c, CURLOPT_HTTPAUTH, CURLAUTH_ANY); - if(cRet != CURLE_OK) - Warning("Failed setting libcurl acceptable http authenication methods: %s", curl_easy_strerror(cRet)); + cRet = (*curl_easy_setopt_f)(c, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + 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(c); + 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(c, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &dSize); - if(cRet != CURLE_OK) { + cRet = (*curl_easy_getinfo_f)(c, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &dSize); + 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 { - Fatal("Unable to get the size of the image"); + Error("Unable to get the size of the image"); + tRet = -60; + return (void*)tRet; } /* 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; } /* Unlock */ unlock(); @@ -470,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(cRet)); - if(attempt < CURL_MAXRETRY) { + Error("cURL Request failed: %s",(*curl_easy_strerror_f)(cRet)); + if (attempt < CURL_MAXRETRY) { Error("Retrying.. Attempt %d of %d",attempt,CURL_MAXRETRY); /* Do a reset */ lock(); @@ -489,11 +601,11 @@ void* cURLCamera::thread_func() { tRet = -50; } } - + /* Cleanup */ - curl_easy_cleanup(c); - c = NULL; - + (*curl_easy_cleanup_f)(c); + c = nullptr; + return (void*)tRet; } @@ -521,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 816937a65..302d8f877 100644 --- a/src/zm_curl_camera.h +++ b/src/zm_curl_camera.h @@ -20,16 +20,13 @@ #ifndef ZM_CURL_CAMERA_H #define ZM_CURL_CAMERA_H -#if HAVE_LIBCURL - -#include "zm_camera.h" -#include "zm_ffmpeg.h" +#include "zm_config.h" #include "zm_buffer.h" -#include "zm_regexp.h" -#include "zm_utils.h" -#include "zm_signal.h" -#include +#include "zm_camera.h" #include +#include + +#if HAVE_LIBCURL #if HAVE_CURL_CURL_H #include @@ -64,22 +61,35 @@ protected: pthread_cond_t request_complete_cond; public: - cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, unsigned int p_width, unsigned int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); + cURLCamera( + const Monitor* monitor, + const std::string &path, + const std::string &username, + const std::string &password, + unsigned int p_width, + unsigned int p_height, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio + ); ~cURLCamera(); - const std::string &Path() const { return( mPath ); } - const std::string &Username() const { return( mUser ); } - const std::string &Password() const { return( mPass ); } + const std::string &Path() const { return mPath; } + const std::string &Username() const { return mUser; } + const std::string &Password() const { return mPass; } void Initialise(); void Terminate(); - int Close() { return 0; }; + int Close() override { return 0; }; - int PrimeCapture(); - int PreCapture(); - int Capture( Image &image ); - int PostCapture(); - int CaptureAndRecord( Image &image, struct timeval recording, char* event_directory ); + int PrimeCapture() override; + int PreCapture() override; + int Capture(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 77c149f03..cc0797c12 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -16,15 +16,15 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // - -#include -#include - -#include "zm.h" #include "zm_db.h" +#include "zm_logger.h" +#include "zm_signal.h" +#include + MYSQL dbconn; -RecursiveMutex db_mutex; +std::mutex db_mutex; +zmDbQueue dbQueue; bool zmDbConnected = false; @@ -40,112 +40,252 @@ bool zmDbConnect() { Error("Can't initialise database connection: %s", mysql_error(&dbconn)); return false; } - my_bool reconnect = 1; + + bool reconnect = 1; if ( mysql_options(&dbconn, MYSQL_OPT_RECONNECT, &reconnect) ) Error("Can't set database auto reconnect option: %s", mysql_error(&dbconn)); - if ( !staticConfig.DB_SSL_CA_CERT.empty() ) + + if ( !staticConfig.DB_SSL_CA_CERT.empty() ) { mysql_ssl_set(&dbconn, staticConfig.DB_SSL_CLIENT_KEY.c_str(), staticConfig.DB_SSL_CLIENT_CERT.c_str(), staticConfig.DB_SSL_CA_CERT.c_str(), - NULL, NULL); + nullptr, nullptr); + } + std::string::size_type colonIndex = staticConfig.DB_HOST.find(":"); if ( colonIndex == std::string::npos ) { - if ( !mysql_real_connect(&dbconn, staticConfig.DB_HOST.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, 0, NULL, 0) ) { - Error( "Can't connect to server: %s", mysql_error(&dbconn)); + if ( !mysql_real_connect( + &dbconn, + staticConfig.DB_HOST.c_str(), + staticConfig.DB_USER.c_str(), + staticConfig.DB_PASS.c_str(), + nullptr, 0, nullptr, 0) ) { + Error("Can't connect to server: %s", mysql_error(&dbconn)); + mysql_close(&dbconn); return false; } } else { - std::string dbHost = staticConfig.DB_HOST.substr( 0, colonIndex ); - std::string dbPortOrSocket = staticConfig.DB_HOST.substr( colonIndex+1 ); + std::string dbHost = staticConfig.DB_HOST.substr(0, colonIndex); + std::string dbPortOrSocket = staticConfig.DB_HOST.substr(colonIndex+1); if ( dbPortOrSocket[0] == '/' ) { - if ( !mysql_real_connect(&dbconn, NULL, staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, 0, dbPortOrSocket.c_str(), 0) ) { + if ( !mysql_real_connect( + &dbconn, + nullptr, + staticConfig.DB_USER.c_str(), + staticConfig.DB_PASS.c_str(), + nullptr, 0, dbPortOrSocket.c_str(), 0) ) { Error("Can't connect to server: %s", mysql_error(&dbconn)); + mysql_close(&dbconn); return false; } } else { - if ( !mysql_real_connect( &dbconn, dbHost.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, atoi(dbPortOrSocket.c_str()), NULL, 0 ) ) { - Error( "Can't connect to server: %s", mysql_error( &dbconn ) ); + if ( !mysql_real_connect( + &dbconn, + dbHost.c_str(), + staticConfig.DB_USER.c_str(), + staticConfig.DB_PASS.c_str(), + nullptr, + atoi(dbPortOrSocket.c_str()), + nullptr, 0) ) { + Error("Can't connect to server: %s", mysql_error(&dbconn)); + mysql_close(&dbconn); return false; } } } - if ( mysql_select_db( &dbconn, staticConfig.DB_NAME.c_str() ) ) { - Error( "Can't select database: %s", mysql_error( &dbconn ) ); + if ( mysql_select_db(&dbconn, staticConfig.DB_NAME.c_str()) ) { + Error("Can't select database: %s", mysql_error(&dbconn)); + mysql_close(&dbconn); return false; } + if ( mysql_query(&dbconn, "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED") ) { + Error("Can't set isolation level: %s", mysql_error(&dbconn)); + } + mysql_set_character_set(&dbconn, "utf8"); zmDbConnected = true; return zmDbConnected; } void zmDbClose() { - if ( zmDbConnected ) { - db_mutex.lock(); - mysql_close( &dbconn ); + if (zmDbConnected) { + std::lock_guard lck(db_mutex); + mysql_close(&dbconn); // mysql_init() call implicitly mysql_library_init() but // mysql_close() does not call mysql_library_end() mysql_library_end(); zmDbConnected = false; - db_mutex.unlock(); } } -MYSQL_RES * zmDbFetch(const char * query) { - if ( !zmDbConnected ) { +MYSQL_RES *zmDbFetch(const std::string &query) { + std::lock_guard lck(db_mutex); + if (!zmDbConnected) { Error("Not connected."); - return NULL; + return nullptr; } - db_mutex.lock(); - if ( mysql_query(&dbconn, query) ) { - db_mutex.unlock(); + if (mysql_query(&dbconn, query.c_str())) { Error("Can't run query: %s", mysql_error(&dbconn)); - return NULL; + return nullptr; } - Debug(4, "Success running query: %s", query); MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %s for query %s", mysql_error(&dbconn), query); + if (!result) { + Error("Can't use query result: %s for query %s", mysql_error(&dbconn), query.c_str()); } - db_mutex.unlock(); 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) ) { + if (row->fetch(query)) { return row; } delete row; - return NULL; + 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; + 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); + if (n_rows != 1) { + 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 = NULL; + result_set = nullptr; return result_set; } row = mysql_fetch_row(result_set); - if ( ! row ) { + if (!row) { mysql_free_result(result_set); - result_set = NULL; - Error("Error getting row from query %s. Error is %s", query, mysql_error(&dbconn)); + result_set = nullptr; + Error("Error getting row from query %s. Error is %s", query.c_str(), mysql_error(&dbconn)); } else { Debug(5, "Success"); } return result_set; } -zmDbRow::~zmDbRow() { - if ( result_set ) { - mysql_free_result(result_set); - result_set = NULL; +int zmDbDo(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) { + 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 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 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); + result_set = nullptr; + } + row = nullptr; +} + +zmDbQueue::zmDbQueue() : + mThread(&zmDbQueue::process, this), + mTerminate(false) +{ } + +zmDbQueue::~zmDbQueue() { + stop(); +} + +void zmDbQueue::stop() { + mTerminate = true; + mCondition.notify_all(); + if (mThread.joinable()) mThread.join(); +} + +void zmDbQueue::process() { + std::unique_lock lock(mMutex); + + while (!mTerminate and !zm_terminate) { + if (mQueue.empty()) { + mCondition.wait(lock); + } + while (!mQueue.empty()) { + if (mQueue.size() > 30) { + Logger *log = Logger::fetch(); + Logger::Level db_level = log->databaseLevel(); + log->databaseLevel(Logger::NOLOG); + Warning("db queue size has grown larger %zu than 20 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); + 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 c707ad2b7..7343ac75b 100644 --- a/src/zm_db.h +++ b/src/zm_db.h @@ -20,17 +20,37 @@ #ifndef ZM_DB_H #define ZM_DB_H +#include +#include #include -#include "zm_thread.h" +#include +#include +#include +#include + +class zmDbQueue { + private: + std::queue mQueue; + std::thread mThread; + std::mutex mMutex; + std::condition_variable mCondition; + bool mTerminate; + public: + zmDbQueue(); + ~zmDbQueue(); + void push(std::string &&sql); + void process(); + void stop(); +}; class zmDbRow { private: MYSQL_RES *result_set; MYSQL_ROW row; public: - zmDbRow() { result_set = NULL; row = NULL; }; - MYSQL_RES *fetch( const char *query ); - zmDbRow( MYSQL_RES *, MYSQL_ROW *row ); + zmDbRow() : result_set(nullptr), row(nullptr) { }; + MYSQL_RES *fetch(const std::string &query); + zmDbRow(MYSQL_RES *, MYSQL_ROW *row); ~zmDbRow(); MYSQL_ROW mysql_row() const { return row; }; @@ -41,12 +61,20 @@ class zmDbRow { }; extern MYSQL dbconn; -extern RecursiveMutex db_mutex; +extern std::mutex db_mutex; +extern zmDbQueue dbQueue; + +extern bool zmDbConnected; bool zmDbConnect(); void zmDbClose(); +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 new file mode 100644 index 000000000..2c23ab3b9 --- /dev/null +++ b/src/zm_define.h @@ -0,0 +1,53 @@ +/* + * This file is part of the ZoneMinder Project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef ZONEMINDER_SRC_ZM_DEFINE_H_ +#define ZONEMINDER_SRC_ZM_DEFINE_H_ + +// These macros have not been adopted by the C++11 standard. +// However glibc 2.17 (CentOS 7) still depends on them to provide the macros which are guarded by these defines. +#if !defined(__STDC_FORMAT_MACROS) +# define __STDC_FORMAT_MACROS +#endif +#if !defined(__STDC_CONSTANT_MACROS) +# define __STDC_CONSTANT_MACROS +#endif + +#include +#include + +typedef std::int64_t int64; +typedef std::int32_t int32; +typedef std::int16_t int16; +typedef std::int8_t int8; +typedef std::uint64_t uint64; +typedef std::uint32_t uint32; +typedef std::uint16_t uint16; +typedef std::uint8_t uint8; + +#ifndef FALLTHROUGH +#if defined(__clang__) +#define FALLTHROUGH [[clang::fallthrough]] +#elif defined(__GNUC__) && __GNUC__ >= 7 +#define FALLTHROUGH [[gnu::fallthrough]] +#else +#define FALLTHROUGH +#endif +#endif + +#endif // ZONEMINDER_SRC_ZM_DEFINE_H_ diff --git a/src/zm_event.cpp b/src/zm_event.cpp index e564479f3..3ef7ea887 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -1,5 +1,5 @@ // -// ZoneMinder Event Class Implementation, $Date$, $Revision$ +// ZoneMinder Event Class Implementation // Copyright (C) 2001-2008 Philip Coombes // // This program is free software; you can redistribute it and/or @@ -17,277 +17,189 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "zm.h" -#include "zm_db.h" -#include "zm_time.h" -#include "zm_signal.h" #include "zm_event.h" -#include "zm_monitor.h" -//#define USE_PREPARED_SQL 1 +#include "zm_camera.h" +#include "zm_db.h" +#include "zm_frame.h" +#include "zm_logger.h" +#include "zm_monitor.h" +#include "zm_signal.h" +#include "zm_videostore.h" + +#include +#include +#include +#include const char * Event::frame_type_names[3] = { "Normal", "Bulk", "Alarm" }; +#define MAX_DB_FRAMES 100 int Event::pre_alarm_count = 0; -Event::PreAlarmData Event::pre_alarm_data[MAX_PRE_ALARM_FRAMES] = { { 0 } }; +Event::PreAlarmData Event::pre_alarm_data[MAX_PRE_ALARM_FRAMES] = {}; Event::Event( Monitor *p_monitor, - struct timeval p_start_time, + SystemTimePoint p_start_time, const std::string &p_cause, - const StringSetMap &p_noteSetMap, - bool p_videoEvent ) : + const StringSetMap &p_noteSetMap + ) : + id(0), monitor(p_monitor), start_time(p_start_time), + end_time(), cause(p_cause), noteSetMap(p_noteSetMap), - videoEvent(p_videoEvent), - videowriter(NULL) + frames(0), + alarm_frames(0), + alarm_frame_written(false), + tot_score(0), + max_score(-1), + //path(""), + //snapshit_file(), + //alarm_file(""), + videoStore(nullptr), + //video_file(""), + //video_path(""), + last_db_frame(0), + have_video_keyframe(false), + //scheme + save_jpegs(0), + terminate_(false) { - std::string notes; createNotes(notes); - struct timeval now; - gettimeofday(&now, 0); + SystemTimePoint now = std::chrono::system_clock::now(); - bool untimedEvent = false; - if ( !start_time.tv_sec ) { - untimedEvent = true; + 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 ) { - Error( - "StartTime in the future %u.%u > %u.%u", - start_time.tv_sec, start_time.tv_usec, now.tv_sec, now.tv_usec - ); + } else if (start_time > now) { + char buffer[26]; + char buffer_now[26]; + 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); + + 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. Difference: %" PRIi64 " s\nstarttime: %s\nnow: %s", + static_cast(std::chrono::duration_cast(now - start_time).count()), + buffer, buffer_now); start_time = now; } - Storage * storage = monitor->getStorage(); - scheme = storage->Scheme(); - unsigned int state_id = 0; - zmDbRow dbrow; - if ( dbrow.fetch("SELECT Id FROM States WHERE IsActive=1") ) { - state_id = atoi(dbrow[0]); + { + zmDbRow dbrow; + if (dbrow.fetch("SELECT Id FROM States WHERE IsActive=1")) { + state_id = atoi(dbrow[0]); + } } - char sql[ZM_SQL_MED_BUFSIZ]; - struct tm *stime = localtime(&start_time.tv_sec); - snprintf(sql, sizeof(sql), "INSERT INTO Events ( MonitorId, StorageId, Name, StartTime, Width, Height, Cause, Notes, StateId, Orientation, Videoed, DefaultVideo, SaveJPEGs, Scheme ) VALUES ( %d, %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', %d, %d, %d, '', %d, '%s' )", + // Copy it in case opening the mp4 doesn't work we can set it to another value + save_jpegs = monitor->GetOptSaveJPEGs(); + Storage *storage = monitor->getStorage(); + 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(%" 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(), notes.c_str(), state_id, monitor->getOrientation(), - videoEvent, - monitor->GetOptSaveJPEGs(), + 0, + video_incomplete_file.c_str(), + save_jpegs, storage->SchemeString().c_str() ); - db_mutex.lock(); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't insert event: %s. sql was (%s)", mysql_error(&dbconn), sql); - db_mutex.unlock(); - return; - } - id = mysql_insert_id(&dbconn); - db_mutex.unlock(); - if ( untimedEvent ) { - Warning("Event %d has zero time, setting to current", id); - } - end_time.tv_sec = 0; - frames = 0; - alarm_frames = 0; - tot_score = 0; - max_score = 0; - alarm_frame_written = false; + id = zmDbDoInsert(sql); - char id_file[PATH_MAX]; - - char *path_ptr = path; - path_ptr += snprintf(path_ptr, sizeof(path), "%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, 0755) ) { - if ( errno != EEXIST ) - Error("Can't mkdir %s: %s", path, strerror(errno)); - } - - if ( storage->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; - - char date_path[PATH_MAX] = ""; - char time_path[PATH_MAX] = ""; - char *time_path_ptr = time_path; - for ( unsigned int i = 0; i < sizeof(dt_parts)/sizeof(*dt_parts); i++ ) { - path_ptr += snprintf(path_ptr, sizeof(path)-(path_ptr-path), "/%02d", dt_parts[i]); - - errno = 0; - if ( mkdir(path, 0755) ) { - // FIXME This should not be fatal. Should probably move to a different storage area. - if ( errno != EEXIST ) - Error("Can't mkdir %s: %s", path, strerror(errno)); - } - if ( i == 2 ) - strncpy(date_path, path, sizeof(date_path)); - else if ( i >= 3 ) - time_path_ptr += snprintf(time_path_ptr, sizeof(time_path)-(time_path_ptr-time_path), "%s%02d", i>3?"/":"", dt_parts[i]); - } - // Create event id symlink - snprintf(id_file, sizeof(id_file), "%s/.%" PRIu64, date_path, id); - if ( symlink(time_path, id_file) < 0 ) - Error("Can't symlink %s -> %s: %s", id_file, path, strerror(errno)); - } else if ( storage->Scheme() == Storage::MEDIUM ) { - path_ptr += snprintf( - path_ptr, sizeof(path), "/%04d-%02d-%02d", - stime->tm_year+1900, stime->tm_mon+1, stime->tm_mday - ); - if ( mkdir(path, 0755) ) { - if ( errno != EEXIST ) - Error("Can't mkdir %s: %s", path, strerror(errno)); - } - path_ptr += snprintf(path_ptr, sizeof(path), "/%" PRIu64, id); - if ( mkdir(path, 0755) ) { - if ( errno != EEXIST ) - Error("Can't mkdir %s: %s", path, strerror(errno)); - } - } else { - path_ptr += snprintf(path_ptr, sizeof(path), "/%" PRIu64, id); - if ( mkdir(path, 0755) ) { - if ( errno != EEXIST ) - Error("Can't mkdir %s: %s", path, strerror(errno)); - } - - // Create empty id tag file - snprintf(id_file, sizeof(id_file), "%s/.%" PRIu64, path, id); - if ( FILE *id_fp = fopen(id_file, "w") ) - fclose(id_fp); - else - Error("Can't fopen %s: %s", id_file, strerror(errno)); - } // deep storage or not - - last_db_frame = 0; - - video_name[0] = 0; - - snprintf(snapshot_file, sizeof(snapshot_file), "%s/snapshot.jpg", path); - snprintf(alarm_file, sizeof(alarm_file), "%s/alarm.jpg", path); - - /* Save as video */ - - if ( monitor->GetOptVideoWriter() != 0 ) { - snprintf(video_name, sizeof(video_name), "%" PRIu64 "-%s", id, "video.mp4"); - snprintf(video_file, sizeof(video_file), staticConfig.video_file_format, path, video_name); - Debug(1,"Writing video file to %s", video_file ); - - /* X264 MP4 video writer */ - if ( monitor->GetOptVideoWriter() == Monitor::X264ENCODE ) { -#if ZM_HAVE_VIDEOWRITER_X264MP4 - videowriter = new X264MP4Writer(video_file, monitor->Width(), monitor->Height(), monitor->Colours(), monitor->SubpixelOrder(), monitor->GetOptEncoderParams()); -#else - Error("ZoneMinder was not compiled with the X264 MP4 video writer, check dependencies (x264 and mp4v2)"); -#endif - } - - if ( videowriter != NULL ) { - /* Open the video stream */ - int nRet = videowriter->Open(); - if ( nRet != 0 ) { - Error("Failed opening video stream"); - delete videowriter; - videowriter = NULL; - } - } - } else { - /* No video object */ - videowriter = NULL; - } - -} // Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent ) + thread_ = std::thread(&Event::Run, this); +} 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. + Debug(1, "Deleting event, calling stop"); + Stop(); + if (thread_.joinable()) { + // Should be. Issuing the stop and then getting the lock + Debug(1, "Joinable"); + thread_.join(); + } else { + Debug(1, "Not Joinable"); + } /* Close the video file */ - if ( videowriter != NULL ) { - int nRet = videowriter->Close(); - if ( nRet != 0 ) { - Error("Failed closing video stream"); + // 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. + 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; } - delete videowriter; - videowriter = NULL; } - 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 0 // This closing frame has no image. There is no point in adding a db record for it, I think. ICON - if ( frames > last_db_frame ) { - frames ++; - Debug(1, "Adding closing frame %d to DB", frames); - frame_data.push(new Frame(id, frames, NORMAL, end_time, delta_time, 0)); + // endtime is set in AddFrame, so SHOULD be set to the value of the last frame timestamp. + 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(); } -#endif - 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(); - - // update frame deltas to refer to video start time which may be a few frames before event start - struct timeval video_offset = {0}; - struct timeval video_start_time = monitor->GetVideoWriterStartTime(); - if (video_start_time.tv_sec > 0) { - timersub(&video_start_time, &start_time, &video_offset); - Debug(1, "Updating frames delta by %d sec %d usec", - video_offset.tv_sec, video_offset.tv_usec); - UpdateFramesDelta(video_offset.tv_sec + video_offset.tv_usec*1e-6); - } - else { - Debug(3, "Video start_time %d sec %d usec not valid -- frame deltas not updated", - video_start_time.tv_sec, video_start_time.tv_usec); } - // Should not be static because we might be multi-threaded - char sql[ZM_SQL_LGE_BUFSIZ]; - snprintf(sql, sizeof(sql), - "UPDATE Events SET Name='%s %" PRIu64 "', EndTime = from_unixtime( %ld ), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo = '%s' WHERE Id = %" PRIu64, - monitor->EventPrefix(), id, end_time.tv_sec, - delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, + 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, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, - video_name, id ); - db_mutex.lock(); - while ( mysql_query(&dbconn, sql) && !zm_terminate ) { - db_mutex.unlock(); - Error("Can't update event: %s reason: %s", sql, mysql_error(&dbconn)); - sleep(1); - db_mutex.lock(); - } - db_mutex.unlock(); + 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) { @@ -304,64 +216,36 @@ void Event::createNotes(std::string ¬es) { } } // void Event::createNotes(std::string ¬es) -bool Event::WriteFrameImage( - Image *image, - struct timeval timestamp, - const char *event_file, - bool alarm_frame) { +void Event::addNote(const char *cause, const std::string ¬e) { + noteSetMap[cause].insert(note); +} +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 + 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; } -bool Event::WriteFrameVideo( - const Image *image, - const struct timeval timestamp, - VideoWriter* videow) { - const Image* frameimg = image; - Image ts_image; - - /* Checking for invalid parameters */ - if ( videow == NULL ) { - Error("NULL Video object"); +bool Event::WritePacket(const std::shared_ptr&packet) { + if (videoStore->writePacket(packet) < 0) return false; - } - - /* If the image does not contain a timestamp, add the timestamp */ - if ( !config.timestamp_on_capture ) { - ts_image = *image; - monitor->TimestampImage(&ts_image, ×tamp); - frameimg = &ts_image; - } - - /* Calculate delta time */ - struct DeltaTimeval delta_time3; - DELTA_TIMEVAL(delta_time3, timestamp, start_time, DT_PREC_3); - unsigned int timeMS = (delta_time3.sec * delta_time3.prec) + delta_time3.fsec; - - /* Encode and write the frame */ - if ( videowriter->Encode(frameimg, timeMS) != 0 ) { - Error("Failed encoding video frame"); - } - return true; } // bool Event::WriteFrameVideo @@ -369,32 +253,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; } @@ -405,285 +289,413 @@ 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)); - } - } - - 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); - db_mutex.lock(); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't insert event: %s", mysql_error(&dbconn)); - } - db_mutex.unlock(); -#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::AddPacket(ZMLockedPacket *packetlock) { + std::unique_lock lck(packet_queue_mutex); + packet_queue.push(packetlock); + packet_queue_condition.notify_one(); } -void Event::AddFramesInternal(int n_frames, int start_frame, Image **images, struct timeval **timestamps) { - static char sql[ZM_SQL_LGE_BUFSIZ]; - strncpy(sql, "INSERT INTO `Frames` (`EventId`, `FrameId`, `TimeStamp`, `Delta`) VALUES ", sizeof(sql)); - int frameCount = 0; - for ( int i = start_frame; i < n_frames && i - start_frame < ZM_SQL_BATCH_SIZE; i++ ) { - if ( timestamps[i]->tv_sec <= 0 ) { - Debug(1, "Not adding pre-capture frame %d, zero or less than 0 timestamp", i); - continue; +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) { + videoStore->writePacket(packet); + } else { + Debug(2, "No video keyframe yet, not writing"); } - - frames++; - - if ( monitor->GetOptSaveJPEGs() & 1 ) { - static char event_file[PATH_MAX]; - snprintf(event_file, sizeof(event_file), staticConfig.capture_file_format, path, frames); - Debug(1, "Writing pre-capture frame %d", frames); - WriteFrameImage(images[i], *(timestamps[i]), event_file); - } - //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); - } - - if ( videowriter != NULL ) { - WriteFrameVideo(images[i], *(timestamps[i]), videowriter); - } - - struct DeltaTimeval delta_time; - DELTA_TIMEVAL(delta_time, *(timestamps[i]), start_time, DT_PREC_2); - // Delta is Decimal(8,2) so 6 integer digits and 2 decimal digits - if ( delta_time.sec > 999999 ) { - Warning("Invalid delta_time from_unixtime(%ld), %s%ld.%02ld", - timestamps[i]->tv_sec, - (delta_time.positive?"":"-"), - delta_time.sec, - delta_time.fsec); - delta_time.sec = 0; - } - - int sql_len = strlen(sql); - snprintf(sql+sql_len, sizeof(sql)-sql_len, "( %" PRIu64 ", %d, from_unixtime(%ld), %s%ld.%02ld ), ", - id, frames, timestamps[i]->tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec); - - frameCount++; - } // end foreach frame - - if ( frameCount ) { - Debug(1, "Adding %d/%d frames to DB", frameCount, n_frames); - *(sql+strlen(sql)-2) = '\0'; - db_mutex.lock(); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't insert frames: %s, sql was (%s)", mysql_error(&dbconn), sql); - } - db_mutex.unlock(); - last_db_frame = frames; - } else { - Debug(1, "No valid pre-capture frames to add"); + //FIXME if it fails, we should write a jpeg } -} // void Event::AddFramesInternal(int n_frames, int start_frame, Image **images, struct timeval **timestamps) + + 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() { - static char sql[ZM_SQL_LGE_BUFSIZ]; - char * sql_ptr = (char *)&sql; - sql_ptr += snprintf(sql, sizeof(sql), - "INSERT INTO Frames ( EventId, FrameId, Type, TimeStamp, Delta, Score ) VALUES " - ); - while ( frame_data.size() ) { + std::string frame_insert_sql = "INSERT INTO `Frames` (`EventId`, `FrameId`, `Type`, `TimeStamp`, `Delta`, `Score`) VALUES "; + std::string stats_insert_sql = "INSERT INTO `Stats` (`EventId`, `FrameId`, `MonitorId`, `ZoneId`, " + "`PixelDiff`, `AlarmPixels`, `FilterPixels`, `BlobPixels`," + "`Blobs`,`MinBlobSize`, `MaxBlobSize`, " + "`MinX`, `MinY`, `MaxX`, `MaxY`,`Score`) VALUES "; + + Debug(1, "Inserting %zu frames", frame_data.size()); + while (frame_data.size()) { Frame *frame = frame_data.front(); frame_data.pop(); - sql_ptr += snprintf(sql_ptr, sizeof(sql)-(sql_ptr-(char *)&sql), "( %" PRIu64 ", %d, '%s', from_unixtime( %ld ), %s%ld.%02ld, %d ), ", - id, frame->frame_id, frame_type_names[frame->type], - frame->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)); } - *(sql_ptr-2) = '\0'; - db_mutex.lock(); - if ( mysql_query(&dbconn, sql) ) { - db_mutex.unlock(); - Error("Can't insert frames: %s, sql was %s", mysql_error(&dbconn), sql); - return; - } - db_mutex.unlock(); } // end void Event::WriteDbFrames() -// Subtract an offset time from frames deltas to match with video start time -void Event::UpdateFramesDelta(double offset) { - char sql[ZM_SQL_MED_BUFSIZ]; - - if (offset == 0.0) return; - // the table is set to auto update timestamp so we force it to keep current value - snprintf(sql, sizeof(sql), - "UPDATE Frames SET timestamp = timestamp, Delta = Delta - (%.4f) WHERE EventId = %" PRIu64, - offset, id); - - db_mutex.lock(); - if (mysql_query(&dbconn, sql)) { - db_mutex.unlock(); - Error("Can't update frames: %s, sql was %s", mysql_error(&dbconn), sql); - return; - } - db_mutex.unlock(); - Info("Updating frames delta by %0.2f sec to match video file", offset); -} - - -void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *alarm_image) { - if ( !timestamp.tv_sec ) { - Debug(1, "Not adding new frame, zero timestamp"); +void Event::AddFrame(Image *image, + 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; } frames++; + Monitor::State monitor_state = monitor->GetState(); + bool write_to_db = false; - FrameType frame_type = score>0?ALARM:(score<0?BULK:NORMAL); - // < 0 means no motion detection is being done. - if ( score < 0 ) - score = 0; + FrameType frame_type = ( ( score > 0 ) ? ALARM : ( + ( + ( monitor_state == Monitor::TAPE ) + and + ( config.bulk_frame_interval > 1 ) + and + ( ! (frames % config.bulk_frame_interval) ) + ) ? BULK : NORMAL + ) ); + Debug(1, "Have frame type %s from score(%d) state %d frames %d bulk frame interval %d and mod%d", + frame_type_names[frame_type], score, monitor->GetState(), frames, config.bulk_frame_interval, (frames % config.bulk_frame_interval)); - if ( monitor->GetOptSaveJPEGs() & 1 ) { - static char event_file[PATH_MAX]; - snprintf(event_file, sizeof(event_file), staticConfig.capture_file_format, path, frames); - Debug(1, "Writing capture frame %d to %s", frames, event_file); - if ( !WriteFrameImage(image, timestamp, event_file) ) { - Error("Failed to write frame image"); + if (score < 0) score = 0; + tot_score += score; + + if (image) { + if (save_jpegs & 1) { + std::string event_file = stringtf(staticConfig.capture_file_format.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 > max_score)) { + write_to_db = true; // web ui might show this as thumbnail, so db needs to know about it. + Debug(1, "Writing snapshot to %s", snapshot_file.c_str()); + WriteFrameImage(image, timestamp, snapshot_file.c_str()); + } else { + Debug(1, "Not Writing snapshot because frames %d score %d > max %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) ) { - write_to_db = true; // web ui might show this as thumbnail, so db needs to know about it. - WriteFrameImage(image, timestamp, snapshot_file); - } + // We are writing an Alarm frame + if (frame_type == ALARM) { + // The first frame with a score will be the frame that alarmed the event + if (!alarm_frame_written) { + write_to_db = true; // OD processing will need it, so the db needs to know about it + alarm_frame_written = true; + Debug(1, "Writing alarm image to %s", alarm_file.c_str()); + if (!WriteFrameImage(image, timestamp, alarm_file.c_str())) { + Error("Failed to write alarm frame image to %s", alarm_file.c_str()); + } + } else { + Debug(3, "Not Writing alarm image because alarm frame already written"); + } - // We are writing an Alarm frame - if ( frame_type == ALARM ) { - // The first frame with a score will be the frame that alarmed the event - if ( !alarm_frame_written ) { - write_to_db = true; // OD processing will need it, so the db needs to know about it - alarm_frame_written = true; - WriteFrameImage(image, timestamp, alarm_file); - } - alarm_frames++; - - tot_score += score; - if ( score > (int)max_score ) - max_score = score; - - if ( alarm_image ) { - if ( monitor->GetOptSaveJPEGs() & 2 ) { - static char event_file[PATH_MAX]; - snprintf(event_file, sizeof(event_file), staticConfig.analyse_file_format, path, frames); - Debug(1, "Writing analysis frame %d", frames); - if ( ! WriteFrameImage(alarm_image, timestamp, event_file, true) ) { - Error("Failed to write analysis frame image"); + if (alarm_image and (save_jpegs & 2)) { + std::string event_file = stringtf(staticConfig.analyse_file_format.c_str(), path.c_str(), frames); + Debug(1, "Writing analysis frame %d to %s", frames, event_file.c_str()); + if (!WriteFrameImage(alarm_image, timestamp, event_file.c_str(), true)) { + Error("Failed to write analysis frame image to %s", event_file.c_str()); } } - } - } // end if frame_type == ALARM + } // end if is an alarm frame + } else { + Debug(1, "No image"); + } // end if has image - if ( videowriter != NULL ) { - WriteFrameVideo(image, timestamp, videowriter); + if (frame_type == ALARM) alarm_frames++; + + bool db_frame = ( frame_type == BULK ) + or ( frame_type == ALARM ) + or ( frames == 1 ) + or ( score > max_score ) + or ( monitor_state == Monitor::ALERT ) + or ( monitor_state == Monitor::ALARM ) + or ( monitor_state == Monitor::PREALARM ); + + if (score > max_score) { + max_score = score; } - struct DeltaTimeval delta_time; - DELTA_TIMEVAL(delta_time, timestamp, start_time, DT_PREC_2); + 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()); - bool db_frame = ( frame_type != BULK ) || (frames==1) || ((frames%config.bulk_frame_interval)==0) ; - if ( db_frame ) { - static char sql[ZM_SQL_MED_BUFSIZ]; - - frame_data.push(new Frame(id, frames, frame_type, timestamp, delta_time, score)); - if ( write_to_db || ( frame_data.size() > 20 ) ) { - Debug(1, "Adding %d frames to DB", frame_data.size()); + // The idea is to write out 1/sec + 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() > 5*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; - } - // We are writing a Bulk frame - if ( frame_type == BULK ) { - 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 - ); - db_mutex.lock(); - while ( mysql_query(&dbconn, sql) && !zm_terminate ) { - Error("Can't update event: %s", mysql_error(&dbconn)); - db_mutex.unlock(); - sleep(1); - db_mutex.lock(); - } - db_mutex.unlock(); + 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 end_time = timestamp; -} // end void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *alarm_image) +} + +bool Event::SetPath(Storage *storage) { + scheme = storage->Scheme(); + + path = stringtf("%s/%d", storage->Path(), monitor->Id()); + // Try to make the Monitor Dir. Normally this would exist, but in odd cases might not. + if (mkdir(path.c_str(), 0755) and (errno != EEXIST)) { + Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); + return false; + } + + 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; + + std::string date_path; + std::string time_path; + + for (unsigned int i = 0; i < sizeof(dt_parts)/sizeof(*dt_parts); i++) { + path += stringtf("/%02d", dt_parts[i]); + + if (mkdir(path.c_str(), 0755) and (errno != EEXIST)) { + Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); + return false; + } + if (i == 2) + date_path = path; + } + time_path = stringtf("%02d/%02d/%02d", stime.tm_hour, stime.tm_min, stime.tm_sec); + + // Create event id symlink + std::string id_file = stringtf("%s/.%" PRIu64, date_path.c_str(), id); + if (symlink(time_path.c_str(), id_file.c_str()) < 0) { + Error("Can't symlink %s -> %s: %s", id_file.c_str(), time_path.c_str(), strerror(errno)); + return false; + } + } else if (scheme == Storage::MEDIUM) { + path += stringtf("/%04d-%02d-%02d", + stime.tm_year+1900, stime.tm_mon+1, stime.tm_mday + ); + if (mkdir(path.c_str(), 0755) and (errno != EEXIST)) { + Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); + return false; + } + path += stringtf("/%" PRIu64, id); + if (mkdir(path.c_str(), 0755) and (errno != EEXIST)) { + Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); + return false; + } + } else { + path += stringtf("/%" PRIu64, id); + if (mkdir(path.c_str(), 0755) and (errno != EEXIST)) { + Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); + return false; + } + + // Create empty id tag file + std::string id_file = stringtf("%s/.%" PRIu64, path.c_str(), id); + if ( FILE *id_fp = fopen(id_file.c_str(), "w") ) { + fclose(id_fp); + } else { + Error("Can't fopen %s: %s", id_file.c_str(), strerror(errno)); + return false; + } + } // deep storage or not + return true; +} // end bool Event::SetPath + +void Event::Run() { + Storage *storage = monitor->getStorage(); + if (!SetPath(storage)) { + // Try another + Warning("Failed creating event dir at %s", storage->Path()); + + std::string sql = stringtf("SELECT `Id` FROM `Storage` WHERE `Id` != %u", storage->Id()); + if (monitor->ServerId()) + sql += stringtf(" AND ServerId=%u", monitor->ServerId()); + + storage = nullptr; + + 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)) + break; + delete storage; + storage = nullptr; + } // end foreach row of Storage + mysql_free_result(result); + result = nullptr; + } + if (!storage) { + Info("No valid local storage area found. Trying all other areas."); + // Try remote + sql = "SELECT `Id` FROM `Storage` WHERE ServerId IS NULL"; + if (monitor->ServerId()) + sql += stringtf(" OR ServerId != %u", monitor->ServerId()); + + 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)) + break; + delete storage; + storage = nullptr; + } // end foreach row of Storage + mysql_free_result(result); + result = nullptr; + } + } + 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); + } // end if ! setPath(Storage) + Debug(1, "Using storage area at %s", path.c_str()); + + snapshot_file = path + "/snapshot.jpg"; + alarm_file = path + "/alarm.jpg"; + + video_incomplete_path = path + "/" + video_incomplete_file; + + if (monitor->GetOptVideoWriter() != 0) { + /* Save as video */ + + videoStore = new VideoStore( + video_incomplete_path.c_str(), + container.c_str(), + monitor->GetVideoStream(), + monitor->GetVideoCodecContext(), + ( monitor->RecordAudio() ? monitor->GetAudioStream() : nullptr ), + ( monitor->RecordAudio() ? monitor->GetAudioCodecContext() : nullptr ), + monitor ); + + if ( !videoStore->open() ) { + Warning("Failed to open videostore, turning on jpegs"); + delete videoStore; + videoStore = nullptr; + if ( ! ( save_jpegs & 1 ) ) { + save_jpegs |= 1; // Turn on jpeg storage + zmDbDo(stringtf("UPDATE Events SET SaveJpegs=%d WHERE Id=%" PRIu64, save_jpegs, id)); + } + } else { + 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 + if (storage != monitor->getStorage()) + delete storage; + + std::unique_lock lck(packet_queue_mutex); + + // The idea is to process the queue no matter what so that all packets get processed. + // We only break if the queue is empty + while (true) { + if (!packet_queue.empty()) { + Debug(1, "adding packet"); + const ZMLockedPacket * packet_lock = packet_queue.front(); + this->AddPacket_(packet_lock->packet_); + delete packet_lock; + packet_queue.pop(); + } else { + if (terminate_ or zm_terminate) { +Debug(1, "terminating"); + break; + } +Debug(1, "waiting"); + packet_queue_condition.wait(lck); +Debug(1, "wakeing"); + } + } +} diff --git a/src/zm_event.h b/src/zm_event.h index 77199509c..2029c062a 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -1,69 +1,67 @@ // // ZoneMinder Core Interfaces, $Date$, $Revision$ // Copyright (C) 2001-2008 Philip Coombes -// +// // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// +// #ifndef ZM_EVENT_H #define ZM_EVENT_H -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "zm.h" -#include "zm_image.h" -#include "zm_stream.h" -#include "zm_video.h" +#include "zm_config.h" +#include "zm_define.h" +#include "zm_packet.h" #include "zm_storage.h" +#include "zm_time.h" +#include "zm_utils.h" +#include "zm_zone.h" + +#include +#include +#include +#include +#include +#include +#include +#include + -class Zone; -class Monitor; class EventStream; +class Frame; +class Image; +class Monitor; +class VideoStore; +class ZMPacket; +class Zone; + +// Maximum number of prealarm frames that can be stored +#define MAX_PRE_ALARM_FRAMES 16 -#define MAX_PRE_ALARM_FRAMES 16 // Maximum number of prealarm frames that can be stored typedef uint64_t event_id_t; - typedef enum { NORMAL=0, BULK, ALARM } FrameType; -#include "zm_frame.h" // // Class describing events, i.e. captured periods of activity. // class Event { friend class EventStream; - protected: - static int sd; - - public: + public: typedef std::set StringSet; typedef std::map StringSetMap; - protected: + protected: static const char * frame_type_names[3]; struct PreAlarmData { @@ -79,104 +77,120 @@ 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; - bool videoEvent; int frames; int alarm_frames; bool alarm_frame_written; - unsigned int tot_score; - unsigned int max_score; - char path[PATH_MAX]; - char snapshot_file[PATH_MAX]; - char alarm_file[PATH_MAX]; - VideoWriter* videowriter; - FILE* timecodes_fd; - char video_name[PATH_MAX]; - char video_file[PATH_MAX]; - char timecodes_name[PATH_MAX]; - char timecodes_file[PATH_MAX]; + int tot_score; + int max_score; + std::string path; + std::string snapshot_file; + std::string alarm_file; + VideoStore *videoStore; + + 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; + int save_jpegs; - void createNotes( std::string ¬es ); + void createNotes(std::string ¬es); - public: - static bool OpenFrameSocket( int ); - static bool ValidateFrameSocket( int ); + std::queue packet_queue; + std::mutex packet_queue_mutex; + std::condition_variable packet_queue_condition; - Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent=false ); + void Run(); + + std::atomic terminate_; + std::thread thread_; + + public: + static bool OpenFrameSocket(int); + static bool ValidateFrameSocket(int); + + Event(Monitor *p_monitor, + SystemTimePoint p_start_time, + const std::string &p_cause, + const StringSetMap &p_noteSetMap); ~Event(); uint64_t Id() const { return id; } - const std::string &Cause() { return cause; } + const std::string &Cause() const { return cause; } + void addNote(const char *cause, const std::string ¬e); int Frames() const { return frames; } int AlarmFrames() const { return alarm_frames; } - const struct timeval &StartTime() const { return start_time; } - const struct timeval &EndTime() const { return end_time; } - struct timeval &StartTime() { return start_time; } - struct timeval &EndTime() { return end_time; } + SystemTimePoint StartTime() const { return start_time; } + SystemTimePoint EndTime() const { return end_time; } + TimePoint::duration Duration() const { return end_time - start_time; }; - bool SendFrameImage( const Image *image, bool alarm_frame=false ); - bool WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame=false ); - bool WriteFrameVideo( const Image *image, const struct timeval timestamp, VideoWriter* videow ); + void AddPacket(ZMLockedPacket *); + 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, SystemTimePoint timestamp, const char *event_file, bool alarm_frame = false) const; - void updateNotes( const StringSetMap &stringSetMap ); + void updateNotes(const StringSetMap &stringSetMap); - void AddFrames( int n_frames, Image **images, struct timeval **timestamps ); - void AddFrame( Image *image, struct timeval timestamp, int score=0, Image *alarm_frame=NULL ); + 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 Stop() { + terminate_ = true; + packet_queue_condition.notify_all(); + } + bool Stopped() const { return terminate_; } + + private: void WriteDbFrames(); - void UpdateFramesDelta(double offset); + 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 ); + public: + 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 const char *getSubPath( time_t *time ) { - return Event::getSubPath( localtime( time ) ); + static std::string getSubPath(time_t *time) { + tm time_tm = {}; + localtime_r(time, &time_tm); + return Event::getSubPath(time_tm); } - char* getEventFile(void) { - return video_file; + const char* getEventFile() const { + return video_file.c_str(); } - public: static int PreAlarmCount() { return pre_alarm_count; } static void EmptyPreAlarmFrames() { - if ( pre_alarm_count > 0 ) { - for ( int i = 0; i < MAX_PRE_ALARM_FRAMES; i++ ) { - delete pre_alarm_data[i].image; - delete pre_alarm_data[i].alarm_frame; - } - memset( pre_alarm_data, 0, sizeof(pre_alarm_data) ); - } pre_alarm_count = 0; } - static void AddPreAlarmFrame( Image *image, struct timeval timestamp, int score=0, Image *alarm_frame=NULL ) { - pre_alarm_data[pre_alarm_count].image = new Image( *image ); - pre_alarm_data[pre_alarm_count].timestamp = timestamp; - pre_alarm_data[pre_alarm_count].score = score; - if ( alarm_frame ) { - pre_alarm_data[pre_alarm_count].alarm_frame = new Image( *alarm_frame ); - } + static void AddPreAlarmFrame( + Image *image, + SystemTimePoint timestamp, + int score=0, + Image *alarm_frame=nullptr + ) { pre_alarm_count++; } void SavePreAlarmFrames() { - for ( int i = 0; i < pre_alarm_count; i++ ) { - AddFrame( pre_alarm_data[i].image, pre_alarm_data[i].timestamp, pre_alarm_data[i].score, pre_alarm_data[i].alarm_frame ); - } EmptyPreAlarmFrames(); } }; - #endif // ZM_EVENT_H diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 2ca12972d..28a0e50d6 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -1,5 +1,5 @@ // -// ZoneMinder Event Class Implementation, $Date$, $Revision$ +// ZoneMinder Event Stream Class Implementation // Copyright (C) 2001-2008 Philip Coombes // // This program is free software; you can redistribute it and/or @@ -16,48 +16,40 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "zm.h" -#include "zm_db.h" -#include "zm_time.h" -#include "zm_mpeg.h" -#include "zm_signal.h" -#include "zm_event.h" +// #include "zm_eventstream.h" -#include "zm_storage.h" -#include "zm_monitor.h" +#include "zm_db.h" +#include "zm_image.h" +#include "zm_logger.h" #include "zm_sendfile.h" +#include "zm_signal.h" +#include "zm_storage.h" +#include +#include -bool EventStream::loadInitialEventData(int monitor_id, time_t event_time) { - static char sql[ZM_SQL_SML_BUFSIZ]; +#ifdef __FreeBSD__ +#include +#endif - snprintf(sql, sizeof(sql), "SELECT `Id` FROM `Events` WHERE " - "`MonitorId` = %d AND unix_timestamp(`EndTime`) > %ld " - "ORDER BY `Id` ASC LIMIT 1", monitor_id, event_time); +const std::string EventStream::StreamMode_Strings[4] = { + "None", + "Single", + "All", + "Gapless" +}; - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } +constexpr Milliseconds EventStream::STREAM_PAUSE_WAIT; + +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)); + + 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) ) { @@ -71,35 +63,42 @@ 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; } // bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) -bool EventStream::loadInitialEventData(uint64_t init_event_id, unsigned int init_frame_id) { +bool EventStream::loadInitialEventData( + uint64_t init_event_id, + unsigned int init_frame_id + ) { loadEventData(init_event_id); if ( init_frame_id ) { if ( init_frame_id >= event_data->frame_count ) { - Error("Invalid frame id specified. %d > %d", init_frame_id, event_data->frame_count); + Error("Invalid frame id specified. %d > %lu", init_frame_id, event_data->frame_count); curr_stream_time = event_data->start_time; + curr_frame_id = 1; } else { curr_stream_time = event_data->frames[init_frame_id-1].timestamp; curr_frame_id = init_frame_id; @@ -112,26 +111,19 @@ bool EventStream::loadInitialEventData(uint64_t init_event_id, unsigned int init } bool EventStream::loadEventData(uint64_t event_id) { - static char sql[ZM_SQL_MED_BUFSIZ]; + 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); - snprintf(sql, sizeof(sql), - "SELECT `MonitorId`, `StorageId`, `Frames`, unix_timestamp( `StartTime` ) AS StartTimestamp, " - "(SELECT max(`Delta`)-min(`Delta`) FROM `Frames` WHERE `EventId`=`Events`.`Id`) AS Duration, " - "`DefaultVideo`, `Scheme`, `SaveJPEGs` 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); @@ -147,11 +139,14 @@ 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] == NULL ? 0 : atoi(dbrow[2]); - event_data->start_time = atoi(dbrow[3]); - event_data->duration = dbrow[4] ? atof(dbrow[4]) : 0.0; - strncpy(event_data->video_file, dbrow[5], sizeof(event_data->video_file)-1); - std::string scheme_str = std::string(dbrow[6]); + event_data->frame_count = dbrow[2] == nullptr ? 0 : atoi(dbrow[2]); + 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; } else if ( scheme_str == "Medium" ) { @@ -159,143 +154,168 @@ bool EventStream::loadEventData(uint64_t event_id) { } else { event_data->scheme = Storage::SHALLOW; } - event_data->SaveJPEGs = dbrow[7] == NULL ? 0 : atoi(dbrow[7]); + event_data->SaveJPEGs = dbrow[8] == nullptr ? 0 : atoi(dbrow[8]); + event_data->Orientation = (Monitor::Orientation)(dbrow[9] == nullptr ? 0 : atoi(dbrow[9])); mysql_free_result(result); - Storage * storage = new Storage(event_data->storage_id); + if ( !monitor ) { + monitor = Monitor::Load(event_data->monitor_id, false, Monitor::QUERY); + } else if ( monitor->Id() != event_data->monitor_id ) { + monitor = Monitor::Load(event_data->monitor_id, false, Monitor::QUERY); + } + if ( !monitor ) { + Fatal("Unable to load monitor id %d for streaming", event_data->monitor_id); + } + + if ( !storage ) { + storage = new Storage(event_data->storage_id); + } else if ( storage->Id() != event_data->storage_id ) { + delete storage; + storage = new Storage(event_data->storage_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/%ld/%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/%ld/%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/%ld/%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/%ld/%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/%ld/%" PRIu64, - storage_path, event_data->monitor_id, event_data->event_id); - else - snprintf(event_data->path, sizeof(event_data->path), "%s/%s/%ld/%" PRIu64, - staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, - event_data->event_id); - } - delete storage; storage = NULL; - - updateFrameRate((double)event_data->frame_count/event_data->duration); - Debug(3, "fps set by frame_count(%d)/duration(%f)", - event_data->frame_count, event_data->duration); - - 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)); + 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); + } } - result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %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); + + 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(4, "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; + if ( mysql_errno(&dbconn) ) { Error("Can't fetch row: %s", mysql_error(&dbconn)); exit(mysql_errno(&dbconn)); } - mysql_free_result(result); - if ( event_data->video_file[0] ) { - std::string filepath = std::string(event_data->path) + "/" + std::string(event_data->video_file); - //char filepath[PATH_MAX]; - //snprintf(filepath, sizeof(filepath), "%s/%s", event_data->path, event_data->video_file); + 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 = event_data->path + "/" + event_data->video_file; Debug(1, "Loading video file from %s", filepath.c_str()); + 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 = NULL; + ffmpeg_input = nullptr; } } + // Not sure about this if ( forceEventChange || mode == MODE_ALL_GAPLESS ) { if ( replay_rate > 0 ) curr_stream_time = event_data->frames[0].timestamp; else - curr_stream_time = event_data->frames[event_data->frame_count-1].timestamp; + curr_stream_time = event_data->frames[event_data->last_frame_id-1].timestamp; } - Debug(2, "Event:%" PRIu64 ", Frames:%ld, Duration: %.2f", - event_data->event_id, event_data->frame_count, event_data->duration); + Debug(2, "Event: %" PRIu64 ", Frames: %ld, Last Frame ID (%ld, Duration: %.2f 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 ) @@ -303,14 +323,14 @@ bool EventStream::loadEventData(uint64_t event_id) { void EventStream::processCommand(const CmdMsg *msg) { Debug(2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0]); // Check for incoming command - switch( (MsgCommand)msg->msg_data[0] ) { + switch ( (MsgCommand)msg->msg_data[0] ) { case CMD_PAUSE : Debug(1, "Got PAUSE command"); // 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"); @@ -322,14 +342,16 @@ void EventStream::processCommand(const CmdMsg *msg) { if ( (mode == MODE_SINGLE || mode == MODE_NONE) && - ((unsigned int)curr_frame_id == event_data->frame_count) + ((unsigned int)curr_frame_id == event_data->last_frame_id) ) { Debug(1, "Was in single or no replay mode, and at last frame, so jumping to 1st frame"); curr_frame_id = 1; } else { - Debug(1, "mode is %s, current frame is %d, frame count is %d", - (mode == MODE_SINGLE ? "single" : "not single"), - curr_frame_id, event_data->frame_count ); + Debug(1, "mode is %s, current frame is %ld, frame count is %ld, last frame id is %ld", + StreamMode_Strings[(int) mode].c_str(), + curr_frame_id, + event_data->frame_count, + event_data->last_frame_id); } replay_rate = ZM_RATE_BASE; @@ -340,6 +362,13 @@ void EventStream::processCommand(const CmdMsg *msg) { paused = false; } replay_rate = ntohs(((unsigned char)msg->msg_data[2]<<8)|(unsigned char)msg->msg_data[1])-32768; + if ( replay_rate > 50 * ZM_RATE_BASE ) { + Warning("requested replay rate (%d) is too high. We only support up to 50x", replay_rate); + replay_rate = 50 * ZM_RATE_BASE; + } else if ( replay_rate < -50*ZM_RATE_BASE ) { + Warning("requested replay rate (%d) is too low. We only support up to -50x", replay_rate); + replay_rate = -50 * ZM_RATE_BASE; + } break; case CMD_STOP : Debug(1, "Got STOP command"); @@ -372,22 +401,29 @@ void EventStream::processCommand(const CmdMsg *msg) { } break; case CMD_SLOWFWD : - Debug(1, "Got SLOW FWD command"); paused = true; replay_rate = ZM_RATE_BASE; 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 %ld", curr_frame_id); break; case CMD_SLOWREV : - Debug(1, "Got SLOW REV command"); paused = true; replay_rate = ZM_RATE_BASE; step = -1; + curr_frame_id -= 1; + if ( curr_frame_id < 1 ) curr_frame_id = 1; + Debug(1, "Got SLOWREV command new frame id %ld", curr_frame_id); break; case CMD_FASTREV : Debug(1, "Got FAST REV command"); paused = false; // Set play rate switch ( replay_rate ) { + case -1 * ZM_RATE_BASE : + replay_rate = -2 * ZM_RATE_BASE; + break; case -2 * ZM_RATE_BASE : replay_rate = -5 * ZM_RATE_BASE; break; @@ -402,7 +438,7 @@ void EventStream::processCommand(const CmdMsg *msg) { replay_rate = -50 * ZM_RATE_BASE; break; default : - replay_rate = -2 * ZM_RATE_BASE; + replay_rate = -1 * ZM_RATE_BASE; break; } break; @@ -466,14 +502,14 @@ void EventStream::processCommand(const CmdMsg *msg) { if ( replay_rate >= 0 ) curr_frame_id = 0; else - curr_frame_id = event_data->frame_count+1; + curr_frame_id = event_data->last_frame_id+1; paused = false; forceEventChange = true; break; case CMD_NEXT : Debug(1, "Got NEXT command"); if ( replay_rate >= 0 ) - curr_frame_id = event_data->frame_count+1; + curr_frame_id = event_data->last_frame_id+1; else curr_frame_id = 0; paused = false; @@ -481,10 +517,36 @@ void EventStream::processCommand(const CmdMsg *msg) { break; case CMD_SEEK : { - // offset is in seconds - int offset = ((unsigned char)msg->msg_data[1]<<24)|((unsigned char)msg->msg_data[2]<<16)|((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; - curr_frame_id = (int)(event_data->frame_count*offset/event_data->duration); - Debug(1, "Got SEEK command, to %d (new cfid: %d)", offset, curr_frame_id); + 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]; + + 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)) {} + } + + if ( curr_frame_id < 1 ) { + curr_frame_id = 1; + } else if ( (unsigned long)curr_frame_id > event_data->last_frame_id ) { + curr_frame_id = event_data->last_frame_id; + } + + curr_stream_time = event_data->frames[curr_frame_id-1].timestamp; + Debug(1, "Got SEEK command, to %f 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; } @@ -498,31 +560,34 @@ void EventStream::processCommand(const CmdMsg *msg) { // Do nothing, for now break; } + struct { uint64_t event_id; - int 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.progress = (int)event_data->frames[curr_frame_id-1].offset; + status_data.duration = event_data->duration; + status_data.progress = event_data->frames[curr_frame_id-1].offset; status_data.rate = replay_rate; status_data.zoom = zoom; status_data.paused = paused; - Debug(2, "Event:%" PRIu64 ", Paused:%d, progress:%d Rate:%d, Zoom:%d", - status_data.event_id, - status_data.paused, - status_data.progress, - status_data.rate, - status_data.zoom - ); + Debug(2, "Event:%" PRIu64 ", Duration %f, Paused:%d, progress:%f Rate:%d, Zoom:%d", + 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 ) { @@ -530,42 +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 ) + if (static_cast(msg->msg_data[0]) == CMD_QUIT) { exit(0); + } - updateFrameRate((double)event_data->frame_count/event_data->duration); -} // void EventStream::processCommand(const CmdMsg *msg) + 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) -void EventStream::checkEventLoaded() { - static char sql[ZM_SQL_SML_BUFSIZ]; +bool EventStream::checkEventLoaded() { + std::string sql; if ( curr_frame_id <= 0 ) { - snprintf(sql, sizeof(sql), - "SELECT `Id` FROM `Events` WHERE `MonitorId` = %ld AND `Id` < %" PRIu64 " ORDER BY `Id` DESC LIMIT 1", + sql = stringtf( + "SELECT `Id` FROM `Events` WHERE `MonitorId` = %d AND `Id` < %" PRIu64 " ORDER BY `Id` DESC LIMIT 1", event_data->monitor_id, event_data->event_id); - } else if ( (unsigned int)curr_frame_id > event_data->frame_count ) { - snprintf(sql, sizeof(sql), - "SELECT `Id` FROM `Events` WHERE `MonitorId` = %ld AND `Id` > %" PRIu64 " ORDER BY `Id` ASC LIMIT 1", + } else if ( (unsigned int)curr_frame_id > event_data->last_frame_id ) { + 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; + } + 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; + return false; } // Event change required. if ( forceEventChange || ( (mode != MODE_SINGLE) && (mode != MODE_NONE) ) ) { - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); + Debug(1, "Checking for next event %s", sql.c_str()); + + MYSQL_RES *result = zmDbFetch(sql); + if (!result) { + exit(-1); } - MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); + if ( mysql_num_rows(result) != 1 ) { + Debug(1, "No rows returned for %s", sql.c_str()); } MYSQL_ROW dbrow = mysql_fetch_row(result); @@ -580,122 +658,102 @@ void EventStream::checkEventLoaded() { loadEventData(event_id); - Debug(2, "Current frame id = %d", curr_frame_id); if ( replay_rate < 0 ) // rewind - curr_frame_id = event_data->frame_count; + curr_frame_id = event_data->last_frame_id; else curr_frame_id = 1; - Debug(2, "New frame id = %d", curr_frame_id); + Debug(2, "New frame id = %ld", curr_frame_id); + start = std::chrono::steady_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 curr_frame_id = event_data->frame_count; paused = true; sendTextFrame("No more event data found"); - } // end if found a new event or not + } // end if found a new event or not mysql_free_result(result); forceEventChange = false; } else { - Debug(2, "Pausing because mode is %d", mode); + Debug(2, "Pausing because mode is %s", StreamMode_Strings[mode].c_str()); if ( curr_frame_id <= 0 ) curr_frame_id = 1; else - curr_frame_id = event_data->frame_count; + curr_frame_id = event_data->last_frame_id; paused = true; } -} // void EventStream::checkEventLoaded() + return false; +} // 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; - FILE *fdj = NULL; + 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 ((frame_type == FRAME_ANALYSIS) && (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 ) { + } else if (event_data->SaveJPEGs & 1) { + filepath = stringtf(staticConfig.capture_file_format.c_str(), event_data->path.c_str(), curr_frame_id); + } else if (!ffmpeg_input) { Fatal("JPEGS not saved. zms is not capable of streaming jpegs from mp4 yet"); return false; } -#if HAVE_LIBAVCODEC if ( type == STREAM_MPEG ) { - Image image(filepath); + Image image(filepath.c_str()); Image *send_image = prepareImage(&image); if ( !vid_stream ) { - vid_stream = new VideoStream("pipe:", format, bitrate, effective_fps, send_image->Colours(), send_image->SubpixelOrder(), send_image->Width(), send_image->Height()); + 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(); } - /* double pts = */ vid_stream->EncodeFrame(send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_us*1000); - } else -#endif // HAVE_LIBAVCODEC - { - static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; + 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(); - int img_buffer_size = 0; - uint8_t *img_buffer = temp_img_buffer; + fprintf(stdout, "--" BOUNDARY "\r\n"); - bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE)) && filepath[0]; - - fprintf(stdout, "--ZoneMinderFrame\r\n"); - - if ( (type != STREAM_JPEG) || (!filepath[0]) ) - send_raw = false; - - if ( send_raw ) { - fdj = fopen(filepath, "rb"); - if ( !fdj ) { - Error("Can't open %s: %s", filepath, strerror(errno)); - return true; // returning false will cause us to terminate. - } -#if HAVE_SENDFILE - if ( fstat(fileno(fdj),&filestat) < 0 ) { - Error("Failed getting information about file %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 - img_buffer_size = fread(img_buffer, 1, sizeof(temp_img_buffer), fdj); -#endif } else { - Image *image = NULL; + Image *image = nullptr; - if ( filepath[0] ) { -Debug(1, "Loading image"); - image = new Image(filepath); + if (!filepath.empty()) { + image = new Image(filepath.c_str()); } else if ( ffmpeg_input ) { // Get the frame from the mp4 input - Debug(1,"Getting frame from ffmpeg"); - AVFrame *frame; FrameData *frame_data = &event_data->frames[curr_frame_id-1]; - 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); @@ -703,224 +761,190 @@ Debug(1, "Loading image"); Error("Failed getting a frame."); return false; } + + // when stored as an mp4, we just have the rotation as a flag in the headers + // so we need to rotate it before outputting + if ( + (monitor->GetOptVideoWriter() == Monitor::PASSTHROUGH) + and + (event_data->Orientation != Monitor::ROTATE_0) + ) { + Debug(2, "Rotating image %d", event_data->Orientation); + switch ( event_data->Orientation ) { + case Monitor::ROTATE_0 : + // No action required + break; + case Monitor::ROTATE_90 : + case Monitor::ROTATE_180 : + case Monitor::ROTATE_270 : + image->Rotate((event_data->Orientation-1)*90); + break; + case Monitor::FLIP_HORI : + case Monitor::FLIP_VERT : + image->Flip(event_data->Orientation==Monitor::FLIP_HORI); + break; + default: + Error("Invalid Orientation: %d", event_data->Orientation); + } + } else { + Debug(2, "Not Rotating image %d", event_data->Orientation); + } // end if have rotation } else { Error("Unable to get a frame"); return false; } - + Image *send_image = prepareImage(image); + static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; + int img_buffer_size = 0; + uint8_t *img_buffer = temp_img_buffer; switch ( type ) { case STREAM_JPEG : send_image->EncodeJpeg(img_buffer, &img_buffer_size); + fputs("Content-Type: image/jpeg\r\n", stdout); break; case STREAM_ZIP : -#if HAVE_ZLIB_H unsigned long zip_buffer_size; send_image->Zip(img_buffer, &zip_buffer_size); img_buffer_size = zip_buffer_size; + fputs("Content-Type: image/x-rgbz\r\n", stdout); break; -#else - Error("zlib is required for zipped images. Falling back to raw image"); - type = STREAM_RAW; -#endif // HAVE_ZLIB_H 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; default: Fatal("Unexpected frame type %d", type); break; } + send_buffer(img_buffer, img_buffer_size); delete image; - image = NULL; - } // end if send_raw or not - - switch ( type ) { - case STREAM_JPEG : - fputs("Content-Type: image/jpeg\r\n", stdout); - break; - case STREAM_RAW : - fputs("Content-Type: image/x-rgb\r\n", stdout); - break; - case STREAM_ZIP : - fputs("Content-Type: image/x-rgbz\r\n", stdout); - break; - default : - Fatal("Unexpected frame type %d", type); - break; - } - - if ( send_raw ) { -#if HAVE_SENDFILE - fprintf(stdout, "Content-Length: %d\r\n\r\n", (int)filestat.st_size); - if ( zm_sendfile(fileno(stdout), fileno(fdj), 0, (int)filestat.st_size) != (int)filestat.st_size ) { - /* sendfile() failed, use standard way instead */ - img_buffer_size = fread( img_buffer, 1, sizeof(temp_img_buffer), fdj ); - if ( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 ) { - fclose(fdj); /* Close the file handle */ - Error("Unable to send raw frame %u: %s", curr_frame_id, strerror(errno)); - return false; - } - } -#else - fprintf(stdout, "Content-Length: %d\r\n\r\n", img_buffer_size); - if ( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 ) { - fclose(fdj); /* Close the file handle */ - Error("Unable to send raw frame %u: %s", curr_frame_id, strerror(errno)); - return false; - } -#endif - fclose(fdj); /* Close the file handle */ - } else { - Debug(3, "Content length: %d", img_buffer_size); - fprintf(stdout, "Content-Length: %d\r\n\r\n", img_buffer_size); - if ( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 ) { - Error("Unable to send stream frame: %s", strerror(errno)); - return false; - } + image = nullptr; } // end if send_raw or not - fputs("\r\n\r\n", stdout); - fflush(stdout); } // end if stream MPEG or other - last_frame_sent = TV_2_FLOAT(now); + + fputs("\r\n", stdout); + fflush(stdout); + last_frame_sent = now; return true; } // bool EventStream::sendFrame( int delta_us ) void EventStream::runStream() { openComms(); - checkInitialised(); + //checkInitialised(); - if ( type == STREAM_JPEG ) - fputs("Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n", stdout); + if (type == STREAM_JPEG) + fputs("Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n\r\n", stdout); - if ( !event_data ) { + if (!event_data) { sendTextFrame("No event data found"); - exit(0); + zm_terminate = true; + return; } - Debug(3, "frame rate is: (%f)", (double)event_data->frame_count/event_data->duration); - updateFrameRate((double)event_data->frame_count/event_data->duration); - gettimeofday(&start, NULL); - uint64_t start_usec = start.tv_sec * 1000000 + start.tv_usec; + 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); + + start = std::chrono::steady_clock::now(); + + SystemTimePoint::duration last_frame_offset = Seconds(0); + SystemTimePoint::duration time_to_event = Seconds(0); while ( !zm_terminate ) { - gettimeofday(&now, NULL); + now = std::chrono::steady_clock::now(); - int delta_us = 0; + Microseconds delta = Microseconds(0); send_frame = false; if ( connkey ) { // commands may set send_frame to true while ( checkCommandQueue() && !zm_terminate ) { // The idea is to loop here processing all commands before proceeding. - Debug(1, "Have command queue"); } - Debug(2, "Done command queue"); // 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; } - } else { - Debug(2, "Not checking command queue"); } - // Get current frame data FrameData *frame_data = &event_data->frames[curr_frame_id-1]; - //Info( "cst:%.2f", curr_stream_time ); - //Info( "cfid:%d", curr_frame_id ); - //Info( "fdt:%d", frame_data->timestamp ); if ( !paused ) { - Debug(3, "Not paused at frame %d", curr_frame_id); - - // This next bit is to determine if we are in the current event time wise - // and whether to show an image saying how long until the next event. - bool in_event = true; - double time_to_event = 0; - if ( replay_rate > 0 ) { - time_to_event = event_data->frames[0].timestamp - curr_stream_time; - if ( time_to_event > 0 ) - in_event = false; - } else if ( replay_rate < 0 ) { - time_to_event = curr_stream_time - event_data->frames[event_data->frame_count-1].timestamp; - if ( time_to_event > 0 ) - in_event = false; - } - Debug(1, "replay rate(%d) in_event(%d) time_to_event(%f)=curr_stream_time(%f)-frame timestamp:%f", - replay_rate, in_event, time_to_event, curr_stream_time, event_data->frames[event_data->frame_count-1].timestamp); - if ( !in_event ) { - double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent; - Debug(1, "Ctual delta time = %f = %f - %f", actual_delta_time , TV_2_FLOAT(now) , last_frame_sent); - // > 1 second - if ( actual_delta_time > 1 ) { - Debug(1, "Sending time to next event frame"); - static char frame_text[64]; - snprintf(frame_text, sizeof(frame_text), "Time to next event = %d seconds", (int)time_to_event); - if ( !sendTextFrame(frame_text) ) - zm_terminate = true; - } else { - Debug(1, "Not Sending time to next event frame because actual delta time is %f", actual_delta_time); - } - //else - //{ - // FIXME ICON But we are not paused. We are somehow still in the event? - double sleep_time = (replay_rate>0?1:-1) * ((1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000)); - //double sleep_time = (replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000); - //// 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; - } - curr_stream_time += 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); - - //curr_stream_time += (1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000); - //} - continue; - } // end if !in_event - // Figure out if we should send this frame - Debug(3, "cur_frame_id (%d-1) mod frame_mod(%d)", curr_frame_id, frame_mod); + Debug(3, "not paused at cur_frame_id (%ld-1) mod frame_mod(%d)", curr_frame_id, frame_mod); // If we are streaming and this frame is due to be sent // frame mod defaults to 1 and if we are going faster than max_fps will get multiplied by 2 // so if it is 2, then we send every other frame, if is it 4 then every fourth frame, etc. + if ( (frame_mod == 1) || (((curr_frame_id-1)%frame_mod) == 0) ) { - delta_us = (unsigned int)(frame_data->delta * 1000000); - Debug(3, "frame delta %uus ", delta_us); - // 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 = max(delta_us, 1000000 / maxfps); - Debug(3, "delta %u = base_fps(%f)/effective fps(%f) from 30fps", delta_us, base_fps, effective_fps); send_frame = true; } } else if ( step != 0 ) { - Debug(2, "Paused with step"); + Debug(2, "Paused with step %d", step); // We are paused and are just stepping forward or backward one frame step = 0; send_frame = true; } else if ( !send_frame ) { - // We are paused, not stepping and doing nothing - double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent; - if ( actual_delta_time > MAX_STREAM_DELAY ) { + // We are paused, not stepping and doing nothing, meaning that comms didn't set send_frame to true + if (now - last_frame_sent > MAX_STREAM_DELAY) { // Send keepalive Debug(2, "Sending keepalive frame"); send_frame = true; - //} else { - //Debug(2, "Not Sending keepalive frame"); } - } // end if streaming stepping or doing nothing + } // end if streaming stepping or doing nothing - if ( send_frame ) { - if ( !sendFrame(delta_us) ) { + // time_to_event > 0 means that we are not in the event + if (time_to_event > Seconds(0) and mode == MODE_ALL) { + TimePoint::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 = %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? + 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 == Seconds(0)) { + sleep_time += STREAM_PAUSE_WAIT; + } + + curr_stream_time += sleep_time; + time_to_event -= sleep_time; + 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)) { zm_terminate = true; break; } @@ -928,85 +952,215 @@ void EventStream::runStream() { curr_stream_time = frame_data->timestamp; - if ( !paused ) { + if (!paused) { + // delta is since the last frame + 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 + 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, NULL); - uint64_t now_usec = (now.tv_sec * 1000000 + now.tv_usec); + now = std::chrono::steady_clock::now(); // we incremented by replay_rate, so might have jumped past frame_count - if ( (mode == MODE_SINGLE) && ((unsigned int)curr_frame_id >= event_data->frame_count) ) { + if ( (mode == MODE_SINGLE) && ( + (curr_frame_id < 1 ) + || + ((unsigned int)curr_frame_id >= event_data->frame_count) + ) + ) { 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]; - // frame_data->delta is the time since last frame as a float in seconds - // but what if we are skipping frames? We need the distance from the last frame sent - // Also, what about reverse? needs to be absolute value + if ((unsigned int)curr_frame_id <= event_data->frame_count) { + frame_data = &event_data->frames[curr_frame_id-1]; - // There are two ways to go about this, not sure which is correct. - // 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 + // frame_data->delta is the time since last frame as a float in seconds + // but what if we are skipping frames? We need the distance from the last frame sent + // Also, what about reverse? needs to be absolute value - 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; + // There are two ways to go about this, not sure which is correct. + // 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 != Seconds(0)) { + // We assume that we are going forward and the next frame is in the future. + 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 = Seconds(0); + } + last_frame_offset = frame_data->offset; + + 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; + } + + std::this_thread::sleep_for(delta); + Debug(3, "Done sleeping: %" PRIi64 " us", + static_cast(std::chrono::duration_cast(delta).count())); } - usleep(delta_us); - Debug(3, "Done sleeping: %d usec", delta_us); - } - } + } // end if need to sleep + } else { + Debug(1, "invalid curr_frame_id %ld !< %lu", curr_frame_id, event_data->frame_count); + } // end if not at end of event } else { - delta_us = ((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*(replay_rate?abs(replay_rate*2):2))); + // Paused + 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)", - (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))), - 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); - } - } // end if !paused - //if ( step != 0 )// Adding 0 is cheaper than an if 0 - // curr_frame_id starts at 1 though, so we might skip the first frame? + std::this_thread::sleep_for(delta); + } + // We are paused, so might be stepping + //if ( step != 0 )// Adding 0 is cheaper than an if 0 + // curr_frame_id starts at 1 though, so we might skip the first frame? curr_frame_id += step; + } // end if !paused // Detects when we hit end of event and will load the next event or previous event - checkEventLoaded(); - } // end while ! zm_terminate -#if HAVE_LIBAVCODEC - if ( type == STREAM_MPEG ) + if ( checkEventLoaded() ) { + // Have change of event + + // This next bit is to determine if we are in the current event time wise + // and whether to show an image saying how long until the next event. + if ( replay_rate > 0 ) { + // This doesn't make sense unless we have hit the end of the event. + time_to_event = event_data->frames[0].timestamp - curr_stream_time; + Debug(1, "replay rate (%d) time_to_event (%f 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 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 (type == STREAM_MPEG) { delete vid_stream; -#endif // HAVE_LIBAVCODEC + } closeComms(); -} // void EventStream::runStream() +} // end void EventStream::runStream() -void EventStream::setStreamStart( uint64_t init_event_id, unsigned int init_frame_id=0 ) { - loadInitialEventData(init_event_id, init_frame_id); - if ( !(monitor = Monitor::Load(event_data->monitor_id, false, Monitor::QUERY)) ) { - Fatal("Unable to load monitor id %d for streaming", event_data->monitor_id); - return; +bool EventStream::send_file(const std::string &filepath) { + FILE *fdj = nullptr; + fdj = fopen(filepath.c_str(), "rb"); + if (!fdj) { + 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.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 %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 %ld: %s", curr_frame_id, strerror(errno)); + return false; + } + int rc = zm_sendfile(fileno(stdout), fileno(fdj), 0, (int)filestat.st_size); + if (rc == (int)filestat.st_size) { + // Success + fclose(fdj); /* Close the file handle */ + return true; + } + Warning("Unable to send raw frame %ld: %s rc %d != %d", + curr_frame_id, strerror(errno), rc, (int)filestat.st_size); +#endif + + static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; + + uint8_t *img_buffer = temp_img_buffer; + int 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 %ld: %s", curr_frame_id, strerror(errno)); + return false; + } + + return send_buffer(img_buffer, img_buffer_size); } +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 %ld: %s", curr_frame_id, strerror(errno)); + return false; + } + int rc = fwrite(buffer, size, 1, stdout); + + if ( 1 != rc ) { + Error("Unable to send raw frame %ld: %s %d", curr_frame_id, strerror(errno), rc); + return false; + } + return true; +} // end bool EventStream::send_buffer(uint8_t* buffer, int size) + +void EventStream::setStreamStart( + uint64_t init_event_id, + unsigned int init_frame_id=0) { + loadInitialEventData(init_event_id, init_frame_id); +} // end void EventStream::setStreamStart(init_event_id,init_frame_id=0) + void EventStream::setStreamStart(int monitor_id, time_t event_time) { loadInitialEventData(monitor_id, event_time); - if ( !(monitor = Monitor::Load(event_data->monitor_id, false, Monitor::QUERY)) ) { - Fatal("Unable to load monitor id %d for streaming", monitor_id); - return; - } } diff --git a/src/zm_eventstream.h b/src/zm_eventstream.h index 5e7d91bb2..969e6725d 100644 --- a/src/zm_eventstream.h +++ b/src/zm_eventstream.h @@ -20,108 +20,114 @@ #ifndef ZM_EVENTSTREAM_H #define ZM_EVENTSTREAM_H -#include -#include - -#include "zm_image.h" -#include "zm_stream.h" -#include "zm_video.h" +#include "zm_define.h" #include "zm_ffmpeg_input.h" #include "zm_monitor.h" #include "zm_storage.h" +#include "zm_stream.h" -#ifdef __cplusplus extern "C" { -#endif -#include "libavformat/avformat.h" -#include "libavformat/avio.h" -#include "libavcodec/avcodec.h" -#ifdef __cplusplus +#include +#include +#include } -#endif class EventStream : public StreamBase { public: typedef enum { MODE_NONE, MODE_SINGLE, MODE_ALL, MODE_ALL_GAPLESS } StreamMode; + static const std::string StreamMode_Strings[4]; protected: struct FrameData { //unsigned long id; - double timestamp; - double offset; - double delta; - bool in_db; + SystemTimePoint timestamp; + Microseconds offset; + Microseconds delta; + bool in_db; }; struct EventData { uint64_t event_id; - unsigned long monitor_id; + unsigned int monitor_id; unsigned long storage_id; - unsigned long frame_count; - time_t start_time; - double duration; - char path[PATH_MAX]; - int n_frames; + 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 + 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; StreamMode mode; bool forceEventChange; - int curr_frame_id; - double curr_stream_time; + long curr_frame_id; + SystemTimePoint curr_stream_time; bool send_frame; - struct timeval start; // clock time when started the event + TimePoint start; // clock time when started the event EventData *event_data; - FFmpeg_Input *ffmpeg_input; protected: - bool loadEventData( uint64_t event_id ); - bool loadInitialEventData( uint64_t init_event_id, unsigned int init_frame_id ); - bool loadInitialEventData( int monitor_id, time_t event_time ); + bool loadEventData(uint64_t event_id); + bool loadInitialEventData(uint64_t init_event_id, unsigned int init_frame_id); + bool loadInitialEventData(int monitor_id, SystemTimePoint event_time); - void checkEventLoaded(); - void processCommand( const CmdMsg *msg ); - bool sendFrame( int delta_us ); + bool checkEventLoaded(); + void processCommand(const CmdMsg *msg) override; + bool sendFrame(Microseconds delta); public: - EventStream() { - mode = DEFAULT_MODE; - replay_rate = DEFAULT_RATE; + EventStream() : + mode(DEFAULT_MODE), + forceEventChange(false), + curr_frame_id(0), + send_frame(false), + event_data(nullptr), + storage(nullptr), + ffmpeg_input(nullptr) + {} - forceEventChange = false; - - curr_frame_id = 0; - curr_stream_time = 0.0; - send_frame = false; - - event_data = 0; - - // Used when loading frames from an mp4 - input_codec_context = 0; - input_codec = 0; - - ffmpeg_input = NULL; + ~EventStream() { + if ( event_data ) { + if ( event_data->frames ) { + delete[] event_data->frames; + event_data->frames = nullptr; + } + delete event_data; + event_data = nullptr; + } + if ( storage ) { + delete storage; + storage = nullptr; + } + if ( ffmpeg_input ) { + delete ffmpeg_input; + ffmpeg_input = nullptr; + } } - void setStreamStart( uint64_t init_event_id, unsigned int init_frame_id ); - void setStreamStart( int monitor_id, time_t event_time ); - void setStreamMode( StreamMode p_mode ) { - mode = p_mode; - } - void runStream(); + void setStreamStart(uint64_t init_event_id, unsigned int init_frame_id); + void setStreamStart(int monitor_id, time_t event_time); + void setStreamMode(StreamMode p_mode) { mode = p_mode; } + void runStream() override; Image *getImage(); private: - AVCodecContext *input_codec_context; - AVCodec *input_codec; + bool send_file(const std::string &filepath); + bool send_buffer(uint8_t * buffer, int size); + Storage *storage; + FFmpeg_Input *ffmpeg_input; }; #endif // ZM_EVENTSTREAM_H diff --git a/src/zm_exception.h b/src/zm_exception.h index a02653b88..1bceeed8a 100644 --- a/src/zm_exception.h +++ b/src/zm_exception.h @@ -22,8 +22,7 @@ #include -class Exception -{ +class Exception { protected: typedef enum { INFO, WARNING, ERROR, FATAL } Severity; @@ -32,33 +31,28 @@ protected: Severity mSeverity; public: - Exception( const std::string &message, Severity severity=ERROR ) : mMessage( message ), mSeverity( severity ) + explicit Exception(const std::string &message, const Severity severity=ERROR) : + mMessage(message), + mSeverity(severity) { } -public: - const std::string &getMessage() const - { - return( mMessage ); + const std::string &getMessage() const { + return mMessage; } - Severity getSeverity() const - { - return( mSeverity ); + Severity getSeverity() const { + return mSeverity; } - bool isInfo() const - { - return( mSeverity == INFO ); + bool isInfo() const { + return mSeverity == INFO; } - bool isWarning() const - { + bool isWarning() const { return( mSeverity == WARNING ); } - bool isError() const - { + bool isError() const { return( mSeverity == ERROR ); } - bool isFatal() const - { + bool isFatal() const { return( mSeverity == FATAL ); } }; diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index f0b3dc76e..4fe14051f 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -16,58 +16,59 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include #include "zm_ffmpeg.h" -#include "zm_image.h" -#include "zm_rgb.h" -extern "C" { -#include "libavutil/pixdesc.h" -} -#if HAVE_LIBAVCODEC || HAVE_LIBAVUTIL || HAVE_LIBSWSCALE +#include "zm_logger.h" +#include "zm_rgb.h" +#include "zm_utils.h" + +extern "C" { +#include +} void log_libav_callback(void *ptr, int level, const char *fmt, va_list vargs) { Logger *log = Logger::fetch(); int log_level = 0; - if ( level == AV_LOG_QUIET ) { // -8 + if (level == AV_LOG_QUIET) { // -8 log_level = Logger::NOLOG; - } else if ( level == AV_LOG_PANIC ) { //0 + } else if (level == AV_LOG_PANIC) { //0 log_level = Logger::PANIC; - } else if ( level == AV_LOG_FATAL ) { // 8 + } else if (level == AV_LOG_FATAL) { // 8 log_level = Logger::FATAL; - } else if ( level == AV_LOG_ERROR ) { // 16 + } else if (level == AV_LOG_ERROR) { // 16 log_level = Logger::WARNING; // ffmpeg outputs a lot of errors that don't really affect anything. - //log_level = Logger::ERROR; - } else if ( level == AV_LOG_WARNING ) { //24 + } else if (level == AV_LOG_WARNING) { //24 log_level = Logger::INFO; - //log_level = Logger::WARNING; - } else if ( level == AV_LOG_INFO ) { //32 + } else if (level == AV_LOG_INFO) { //32 log_level = Logger::DEBUG1; - //log_level = Logger::INFO; - } else if ( level == AV_LOG_VERBOSE ) { //40 + } else if (level == AV_LOG_VERBOSE) { //40 log_level = Logger::DEBUG2; - } else if ( level == AV_LOG_DEBUG ) { //48 + } else if (level == AV_LOG_DEBUG) { //48 log_level = Logger::DEBUG3; #ifdef AV_LOG_TRACE - } else if ( level == AV_LOG_TRACE ) { + } else if (level == AV_LOG_TRACE) { log_level = Logger::DEBUG8; #endif #ifdef AV_LOG_MAX_OFFSET - } else if ( level == AV_LOG_MAX_OFFSET ) { + } else if (level == AV_LOG_MAX_OFFSET) { log_level = Logger::DEBUG9; #endif } else { Error("Unknown log level %d", level); } - if ( log ) { + if (log) { char logString[8192]; - vsnprintf(logString, sizeof(logString)-1, fmt, vargs); - int length = strlen(logString); - // ffmpeg logs have a carriage return, so replace it with terminator - logString[length-1] = 0; - log->logPrint(false, __FILE__, __LINE__, log_level, logString); + int length = vsnprintf(logString, sizeof(logString)-1, fmt, vargs); + if (length > 0) { + if (static_cast(length) > sizeof(logString)-1) length = sizeof(logString)-1; + // ffmpeg logs have a carriage return, so replace it with terminator + logString[length-1] = 0; + log->logPrint(false, __FILE__, __LINE__, log_level, "%s", logString); + } else { + log->logPrint(false, __FILE__, __LINE__, AV_LOG_ERROR, "Can't encode log from av. fmt was %s", fmt); + } } } @@ -75,16 +76,16 @@ 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"); } else { - Info("Not enabling ffmpeg logs, as LOG_FFMPEG and/or LOG_DEBUG is disabled in options, or this monitor is not part of your debug targets"); + Debug(1,"Not enabling ffmpeg logs, as LOG_FFMPEG and/or LOG_DEBUG is disabled in options, or this monitor is not part of your debug targets"); av_log_set_level(AV_LOG_QUIET); } -#if !LIBAVFORMAT_VERSION_CHECK(58, 9, 0, 64, 0) +#if !LIBAVFORMAT_VERSION_CHECK(58, 9, 58, 9, 0) av_register_all(); #endif avformat_network_init(); @@ -97,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; @@ -114,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 { @@ -131,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 = NULL; - 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; @@ -219,62 +164,6 @@ simple_round: return av_rescale_q(this_thing, fs_tb, out_tb); } #endif -#endif - -int hacked_up_context2_for_older_ffmpeg(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename) { - AVFormatContext *s = avformat_alloc_context(); - int ret = 0; - - *avctx = NULL; - if (!s) { - av_log(s, AV_LOG_ERROR, "Out of memory\n"); - ret = AVERROR(ENOMEM); - return ret; - } - - if (!oformat) { - if (format) { - oformat = av_guess_format(format, NULL, NULL); - if (!oformat) { - av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format); - ret = AVERROR(EINVAL); - } - } else { - oformat = av_guess_format(NULL, filename, NULL); - if (!oformat) { - ret = AVERROR(EINVAL); - av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n", filename); - } - } - } - - if (ret) { - avformat_free_context(s); - return ret; - } - - s->oformat = oformat; -#if 0 - if (s->oformat->priv_data_size > 0) { - if (s->oformat->priv_class) { - // This looks wrong, we just allocated priv_data and now we are losing the pointer to it.FIXME - *(const AVClass**)s->priv_data = s->oformat->priv_class; - av_opt_set_defaults(s->priv_data); - } else { - s->priv_data = av_mallocz(s->oformat->priv_data_size); - if ( ! s->priv_data) { - av_log(s, AV_LOG_ERROR, "Out of memory\n"); - ret = AVERROR(ENOMEM); - return ret; - } - s->priv_data = NULL; - } -#endif - - if (filename) strncpy(s->filename, filename, sizeof(s->filename)-1); - *avctx = s; - return 0; -} static void zm_log_fps(double d, const char *postfix) { uint64_t v = lrintf(d * 100); @@ -289,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, +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 " 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", NULL, 0); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", nullptr, 0); AVCodecParameters *codec = st->codecpar; -#else - AVCodecContext *codec = st->codec; -#endif Debug(1, " Stream #%d:%d", index, i); @@ -350,20 +257,17 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) Debug(1, "ids [0x%x]", st->id); if (lang) Debug(1, "language (%s)", lang->value); - Debug(1, "frames:%d, frame_size:%d stream timebase: %d/%d", - st->codec_info_nb_frames, codec->frame_size, + Debug(1, "frame_size:%d stream timebase: %d/%d", + codec->frame_size, 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; @@ -375,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; @@ -385,6 +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) { + 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) @@ -425,132 +337,62 @@ int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt) { return 0; } -#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"; +enum AVPixelFormat fix_deprecated_pix_fmt(enum AVPixelFormat fmt) { + // Fix deprecated formats + switch ( fmt ) { + case AV_PIX_FMT_YUVJ422P : + return AV_PIX_FMT_YUV422P; + case AV_PIX_FMT_YUVJ444P : + return AV_PIX_FMT_YUV444P; + case AV_PIX_FMT_YUVJ440P : + return AV_PIX_FMT_YUV440P; + case AV_PIX_FMT_NONE : + case AV_PIX_FMT_YUVJ420P : + return AV_PIX_FMT_YUV420P; + default: + return fmt; + } } -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( 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 +bool is_video_stream(const AVStream * stream) { + if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { return true; } + + Debug(2, "Not a video type %d != %d", stream->codecpar->codec_type, AVMEDIA_TYPE_VIDEO); return false; } -bool is_video_context( 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( 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( 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 0; - } - return 1; -#else - int got_packet = 0; - int ret = avcodec_encode_audio2(context, &packet, NULL, &got_packet); - if ( ret < 0 ) { + if ((ret < 0) and (AVERROR_EOF != ret)) { Error("Error encoding (%d) (%s)", ret, av_err2str(ret)); } - return got_packet; -#endif + return ret; // 1 or 0 } // 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 { @@ -559,116 +401,43 @@ int zm_send_packet_receive_frame( } return ret; } -# else - int frameComplete = 0; - while ( !frameComplete ) { - if ( is_video_context(context) ) { - ret = zm_avcodec_decode_video(context, frame, &frameComplete, &packet); - } else { - ret = avcodec_decode_audio4(context, frame, &frameComplete, &packet); - } - if ( ret < 0 ) { - Error("Unable to decode frame: %s", av_make_error_string(ret).c_str()); - return ret; - } - } // end while !frameComplete -#endif - return 0; -} // end int zm_send_packet_receive_frame(AVCodecContext *context, AVFrame *frame, AVPacket &packet) + // In this api the packet is always consumed, so return packet.bytes + return packet.size; +} // 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 ) { - 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 { - 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 dumpPacket(AVStream *stream, AVPacket *pkt, const char *text) { - char b[10240]; - - double pts_time = (double)av_rescale_q(pkt->pts, - stream->time_base, - AV_TIME_BASE_Q - ) / AV_TIME_BASE; - - snprintf(b, sizeof(b), - " pts: %" PRId64 "=%f, dts: %" PRId64 - ", size: %d, stream_index: %d, flags: %04x, keyframe(%d) pos: %" PRId64 - ", duration: %" -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - PRId64 -#else - "d" -#endif - "\n", - pkt->pts, - pts_time, - pkt->dts, - pkt->size, - pkt->stream_index, - pkt->flags, - pkt->flags & AV_PKT_FLAG_KEY, - pkt->pos, - pkt->duration); - Debug(2, "%s:%d:%s: %s", __FILE__, __LINE__, text, b); -} - -void dumpPacket(AVPacket *pkt, const char *text) { - char b[10240]; - - snprintf(b, sizeof(b), - " pts: %" PRId64 ", dts: %" PRId64 - ", size: %d, stream_index: %d, flags: %04x, keyframe(%d) pos: %" PRId64 - ", duration: %" -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - PRId64 -#else - "d" -#endif - "\n", - pkt->pts, - pkt->dts, - pkt->size, - pkt->stream_index, - pkt->flags, - pkt->flags & AV_PKT_FLAG_KEY, - pkt->pos, - pkt->duration); - Debug(2, "%s:%d:%s: %s", __FILE__, __LINE__, text, b); +void zm_free_codec(AVCodecContext **ctx) { + if (*ctx) { + avcodec_close(*ctx); + // We allocate and copy in newer ffmpeg, so need to free it + avcodec_free_context(ctx); + *ctx = nullptr; + } } void zm_packet_copy_rescale_ts(const AVPacket *ipkt, AVPacket *opkt, const AVRational src_tb, const AVRational dst_tb) { @@ -678,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", @@ -700,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, NULL, 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; @@ -777,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; } @@ -791,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 c65bb38a0..f3a3296a8 100644 --- a/src/zm_ffmpeg.h +++ b/src/zm_ffmpeg.h @@ -15,31 +15,29 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ +*/ #ifndef ZM_FFMPEG_H #define ZM_FFMPEG_H -#include -#include "zm.h" + +#include "zm_config.h" +#include "zm_define.h" extern "C" { - -#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 +#include +#include +#if HAVE_LIBAVUTIL_HWCONTEXT_H + #include +#endif /* LIBAVUTIL_VERSION_CHECK checks for the right version of libav and FFmpeg * The original source is vlc (in modules/codec/avcodec/avcommon_compat.h) @@ -50,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 /* @@ -131,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 @@ -156,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 @@ -188,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 */ +/* 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. @@ -245,39 +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 avformat_alloc_output_context2 -int hacked_up_context2_for_older_ffmpeg(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename); -#define avformat_alloc_output_context2(x,y,z,a) hacked_up_context2_for_older_ffmpeg(x,y,z,a) -#endif - #ifndef av_rescale_delta /** * Rescale a timestamp while preserving known durations. @@ -305,101 +143,99 @@ 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 \ ); -#define zm_dump_video_frame(frame,text) Debug(1, "%s: format %d %s %dx%d linesize:%dx%d pts: %" PRId64, \ +#define zm_dump_video_frame(frame, text) Debug(1, "%s: format %d %s %dx%d linesize:%dx%d pts: %" PRId64 " keyframe: %d", \ text, \ frame->format, \ av_get_pix_fmt_name((AVPixelFormat)frame->format), \ frame->width, \ frame->height, \ frame->linesize[0], frame->linesize[1], \ - frame->pts \ + frame->pts, \ + frame->key_frame \ ); -#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 AV_PACKET_DURATION_FMT PRId64 + +#define CODEC_TYPE(stream) stream->codecpar->codec_type +#define CODEC(stream) stream->codecpar + +#ifndef DBG_OFF +# define ZM_DUMP_PACKET(pkt, text) \ + Debug(2, "%s: pts: %" PRId64 ", dts: %" PRId64 \ + ", size: %d, stream_index: %d, flags: %04x, keyframe(%d) pos: %" PRId64 ", duration: %" AV_PACKET_DURATION_FMT, \ + text,\ + pkt.pts,\ + pkt.dts,\ + pkt.size,\ + pkt.stream_index,\ + pkt.flags,\ + pkt.flags & AV_PKT_FLAG_KEY,\ + pkt.pos,\ + pkt.duration) + +# define ZM_DUMP_STREAM_PACKET(stream, pkt, text) \ + if (logDebugging()) { \ + double pts_time = static_cast(av_rescale_q(pkt.pts, stream->time_base, AV_TIME_BASE_Q)) / AV_TIME_BASE; \ + \ + Debug(2, "%s: pts: %" PRId64 " * %u/%u=%f, dts: %" PRId64 \ + ", size: %d, stream_index: %d, %s flags: %04x, keyframe(%d) pos: %" PRId64", duration: %" AV_PACKET_DURATION_FMT, \ + text, \ + pkt.pts, \ + stream->time_base.num, \ + stream->time_base.den, \ + pts_time, \ + pkt.dts, \ + pkt.size, \ + pkt.stream_index, \ + av_get_media_type_string(CODEC_TYPE(stream)), \ + pkt.flags, \ + pkt.flags & AV_PKT_FLAG_KEY, \ + pkt.pos, \ + pkt.duration); \ + } - void av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb); -#endif -#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) - #define zm_avcodec_decode_video( context, rawFrame, frameComplete, packet ) avcodec_decode_video2( context, rawFrame, frameComplete, packet ) #else - #define zm_avcodec_decode_video(context, rawFrame, frameComplete, packet ) avcodec_decode_video( context, rawFrame, frameComplete, packet->data, packet->size) -#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() +# define ZM_DUMP_PACKET(pkt, text) +# define ZM_DUMP_STREAM_PACKET(stream, pkt, text) #endif -#if ! LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - #define av_frame_free( input_avframe ) av_freep( input_avframe ) -#endif +#define zm_av_packet_unref(packet) av_packet_unref(packet) +#define zm_av_packet_ref(dst, src) av_packet_ref(dst, src) + +#define zm_av_frame_alloc() av_frame_alloc() int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt); +enum AVPixelFormat fix_deprecated_pix_fmt(enum AVPixelFormat ); -bool is_video_stream(AVStream *); -bool is_audio_stream(AVStream *); -bool is_video_context(AVCodec *); -bool is_audio_context(AVCodec *); +bool is_video_stream(const AVStream *); +bool is_audio_stream(const AVStream *); +bool is_video_context(const AVCodec *); +bool is_audio_context(const AVCodec *); int zm_receive_packet(AVCodecContext *context, AVPacket &packet); int zm_send_packet_receive_frame(AVCodecContext *context, AVFrame *frame, AVPacket &packet); int zm_send_frame_receive_packet(AVCodecContext *context, AVFrame *frame, AVPacket &packet); -void dumpPacket(AVStream *, AVPacket *,const char *text=""); -void dumpPacket(AVPacket *,const char *text=""); void zm_packet_copy_rescale_ts(const AVPacket *ipkt, AVPacket *opkt, const AVRational src_tb, const AVRational dst_tb); -#if defined(HAVE_LIBSWRESAMPLE) || defined(HAVE_LIBAVRESAMPLE) -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); - #endif // ZM_FFMPEG_H diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 6578b0b2d..4a0c94ec9 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -17,27 +17,19 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" +#include "zm_ffmpeg_camera.h" + +#include "zm_ffmpeg_input.h" +#include "zm_monitor.h" +#include "zm_packet.h" #include "zm_signal.h" #include "zm_utils.h" -#if HAVE_LIBAVFORMAT - -#include "zm_ffmpeg_camera.h" - extern "C" { -#include "libavutil/time.h" -#if HAVE_LIBAVUTIL_HWCONTEXT_H - #include "libavutil/hwcontext.h" -#endif -#include "libavutil/pixdesc.h" +#include } -#ifndef AV_ERROR_MAX_STRING_SIZE -#define AV_ERROR_MAX_STRING_SIZE 64 -#endif - -#include +TimePoint start_read_time; #if HAVE_LIBAVUTIL_HWCONTEXT_H #if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) @@ -63,39 +55,35 @@ 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) { + 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 fmt; + return AV_PIX_FMT_NONE; + } } #endif #endif #endif FfmpegCamera::FfmpegCamera( - int p_id, + const Monitor *monitor, const std::string &p_path, + const std::string &p_second_path, const std::string &p_method, const std::string &p_options, int p_width, @@ -110,7 +98,7 @@ FfmpegCamera::FfmpegCamera( const std::string &p_hwaccel_name, const std::string &p_hwaccel_device) : Camera( - p_id, + monitor, FFMPEG_SRC, p_width, p_height, @@ -124,41 +112,28 @@ FfmpegCamera::FfmpegCamera( p_record_audio ), mPath(p_path), + mSecondPath(p_second_path), mMethod(p_method), mOptions(p_options), hwaccel_name(p_hwaccel_name), hwaccel_device(p_hwaccel_device) { if ( capture ) { - Initialise(); + FFMPEGInit(); } - mFormatContext = NULL; - mVideoStreamId = -1; - mAudioStreamId = -1; - mVideoCodecContext = NULL; - mAudioCodecContext = NULL; - mVideoCodec = NULL; - mAudioCodec = NULL; - mRawFrame = NULL; - mFrame = NULL; frameCount = 0; mCanCapture = false; - videoStore = NULL; - have_video_keyframe = false; - packetqueue = NULL; error_count = 0; + use_hwaccel = true; #if HAVE_LIBAVUTIL_HWCONTEXT_H - hwFrame = NULL; - hw_device_ctx = NULL; + hw_device_ctx = nullptr; #if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) hw_pix_fmt = AV_PIX_FMT_NONE; #endif #endif -#if HAVE_LIBSWSCALE - mConvertContext = NULL; -#endif + mConvertContext = nullptr; /* 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 ) { @@ -173,149 +148,101 @@ FfmpegCamera::FfmpegCamera( } else { Panic("Unexpected colours: %d", colours); } + } // FfmpegCamera::FfmpegCamera FfmpegCamera::~FfmpegCamera() { Close(); - if ( capture ) { - Terminate(); - } FFMPEGDeInit(); } -void FfmpegCamera::Initialise() { - FFMPEGInit(); -} - -void FfmpegCamera::Terminate() { -} - int FfmpegCamera::PrimeCapture() { + start_read_time = std::chrono::steady_clock::now(); if ( mCanCapture ) { - Info("Priming capture from %s, Closing", mPath.c_str()); + Debug(1, "Priming capture from %s, Closing", mPath.c_str()); Close(); } mVideoStreamId = -1; mAudioStreamId = -1; - Info("Priming capture from %s", mPath.c_str()); + Debug(1, "Priming capture from %s", mPath.c_str()); return OpenFfmpeg(); } int FfmpegCamera::PreCapture() { - // If Reopen was called, then ffmpeg is closed and we need to reopen it. - if ( !mCanCapture ) - return OpenFfmpeg(); - // Nothing to do here return 0; } -int FfmpegCamera::Capture(Image &image) { - if ( !mCanCapture ) { +int FfmpegCamera::Capture(std::shared_ptr &zm_packet) { + if (!mCanCapture) return -1; + + start_read_time = std::chrono::steady_clock::now(); + int ret; + AVFormatContext *formatContextPtr; + + if ( mSecondFormatContext and + ( + av_rescale_q(mLastAudioPTS, mAudioStream->time_base, AV_TIME_BASE_Q) + < + av_rescale_q(mLastVideoPTS, mVideoStream->time_base, AV_TIME_BASE_Q) + ) ) { + // if audio stream is behind video stream, then read from audio, otherwise video + formatContextPtr = mSecondFormatContext; + Debug(4, "Using audio input because audio PTS %" PRId64 " < video PTS %" PRId64, + av_rescale_q(mLastAudioPTS, mAudioStream->time_base, AV_TIME_BASE_Q), + av_rescale_q(mLastVideoPTS, mVideoStream->time_base, AV_TIME_BASE_Q) + ); + } else { + formatContextPtr = mFormatContext; + Debug(4, "Using video input because %" PRId64 " >= %" PRId64, + (mAudioStream?av_rescale_q(mLastAudioPTS, mAudioStream->time_base, AV_TIME_BASE_Q):0), + av_rescale_q(mLastVideoPTS, mVideoStream->time_base, AV_TIME_BASE_Q) + ); + } + + if ((ret = av_read_frame(formatContextPtr, &packet)) < 0) { + if ( + // Check if EOF. + (ret == AVERROR_EOF || (formatContextPtr->pb && formatContextPtr->pb->eof_reached)) || + // Check for Connection failure. + (ret == -110) + ) { + Info("Unable to read packet from stream %d: error %d \"%s\".", + packet.stream_index, ret, av_make_error_string(ret).c_str()); + } else { + Error("Unable to read packet from stream %d: error %d \"%s\".", + packet.stream_index, ret, av_make_error_string(ret).c_str()); + } return -1; } - int ret; - // If the reopen thread has a value, but mCanCapture != 0, then we have just - // reopened the connection to the device, and we can clean up the thread. + AVStream *stream = formatContextPtr->streams[packet.stream_index]; + ZM_DUMP_STREAM_PACKET(stream, packet, "ffmpeg_camera in"); - int frameComplete = false; - while ( !frameComplete && !zm_terminate ) { - ret = av_read_frame(mFormatContext, &packet); - if ( ret < 0 ) { - if ( - // Check if EOF. - ( - ret == AVERROR_EOF - || - (mFormatContext->pb && mFormatContext->pb->eof_reached) - ) || - // Check for Connection failure. - (ret == -110) - ) { - Info("Unable to read packet from stream %d: error %d \"%s\".", - packet.stream_index, ret, av_make_error_string(ret).c_str()); - } else { - Error("Unable to read packet from stream %d: error %d \"%s\".", - packet.stream_index, ret, av_make_error_string(ret).c_str()); - } - return -1; - } - bytes += packet.size; + zm_packet->codec_type = stream->codecpar->codec_type; - int keyframe = packet.flags & AV_PKT_FLAG_KEY; - if ( keyframe ) - have_video_keyframe = true; + bytes += packet.size; + zm_packet->set_packet(&packet); + zm_packet->stream = stream; + zm_packet->pts = av_rescale_q(packet.pts, stream->time_base, AV_TIME_BASE_Q); + if (packet.pts != AV_NOPTS_VALUE) { + if (stream == mVideoStream) { + if (mFirstVideoPTS == AV_NOPTS_VALUE) + mFirstVideoPTS = packet.pts; - Debug(5, "Got packet from stream %d dts (%d) pts(%d)", - packet.stream_index, packet.pts, packet.dts); - // What about audio stream? Maybe someday we could do sound detection... - if ( - (packet.stream_index == mVideoStreamId) - && - (keyframe || have_video_keyframe) - ) { - ret = zm_send_packet_receive_frame(mVideoCodecContext, mRawFrame, packet); - if ( ret < 0 ) { - if ( AVERROR(EAGAIN) != ret ) { - Warning("Unable to receive frame %d: code %d %s. error count is %d", - frameCount, ret, av_make_error_string(ret).c_str(), error_count); - error_count += 1; - if ( error_count > 100 ) { - Error("Error count over 100, going to close and re-open stream"); - return -1; - } - } - zm_av_packet_unref(&packet); - continue; - } - - frameComplete = 1; - zm_dump_video_frame(mRawFrame, "raw frame from decoder"); - -#if HAVE_LIBAVUTIL_HWCONTEXT_H -#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) - if ( - (hw_pix_fmt != AV_PIX_FMT_NONE) - && - (mRawFrame->format == hw_pix_fmt) - ) { - /* retrieve data from GPU to CPU */ - ret = av_hwframe_transfer_data(hwFrame, mRawFrame, 0); - if ( ret < 0 ) { - Error("Unable to transfer frame at frame %d: %s, continuing", - frameCount, av_make_error_string(ret).c_str()); - zm_av_packet_unref(&packet); - continue; - } - zm_dump_video_frame(hwFrame, "After hwtransfer"); - - hwFrame->pts = mRawFrame->pts; - input_frame = hwFrame; - } else { -#endif -#endif - input_frame = mRawFrame; -#if HAVE_LIBAVUTIL_HWCONTEXT_H -#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) - } -#endif -#endif - - if ( transfer_to_image(image, mFrame, input_frame) < 0 ) { - zm_av_packet_unref(&packet); - return -1; - } - - frameCount++; + mLastVideoPTS = packet.pts - mFirstVideoPTS; } else { - Debug(4, "Different stream_index %d", packet.stream_index); - } // end if packet.stream_index == mVideoStreamId - zm_av_packet_unref(&packet); - } // end while ! frameComplete - return frameComplete ? 1 : 0; -} // FfmpegCamera::Capture + if (mFirstAudioPTS == AV_NOPTS_VALUE) + mFirstAudioPTS = packet.pts; + + mLastAudioPTS = packet.pts - mFirstAudioPTS; + } + } + zm_av_packet_unref(&packet); + + return 1; +} // FfmpegCamera::Capture int FfmpegCamera::PostCapture() { // Nothing to do here @@ -325,15 +252,10 @@ int FfmpegCamera::PostCapture() { int FfmpegCamera::OpenFfmpeg() { int ret; - have_video_keyframe = false; error_count = 0; - // Open the input, not necessarily a file -#if !LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 4, 0) - if ( av_open_input_file(&mFormatContext, mPath.c_str(), NULL, 0, NULL) != 0 ) -#else // Handle options - AVDictionary *opts = 0; + AVDictionary *opts = nullptr; ret = av_dict_parse_string(&opts, Options().c_str(), "=", ",", 0); if ( ret < 0 ) { Warning("Could not parse ffmpeg input options '%s'", Options().c_str()); @@ -341,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" ) { @@ -359,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()); @@ -371,35 +292,28 @@ int FfmpegCamera::OpenFfmpeg() { mFormatContext->interrupt_callback.callback = FfmpegInterruptCallback; mFormatContext->interrupt_callback.opaque = this; - ret = avformat_open_input(&mFormatContext, mPath.c_str(), NULL, &opts); - if ( ret != 0 ) -#endif - { - Error("Unable to open input %s due to: %s", mPath.c_str(), + ret = avformat_open_input(&mFormatContext, mPath.c_str(), nullptr, &opts); + if (ret != 0) { + logPrintf(Logger::ERROR + monitor->Importance(), + "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 = NULL; - } -#endif - av_dict_free(&opts); + if (mFormatContext) { + avformat_close_input(&mFormatContext); + mFormatContext = nullptr; + } + av_dict_free(&opts); return -1; } - AVDictionaryEntry *e = NULL; - while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL ) { + AVDictionaryEntry *e = nullptr; + while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr ) { Warning("Option %s not recognized by ffmpeg", e->key); } av_dict_free(&opts); -#if !LIBAVFORMAT_VERSION_CHECK(53, 6, 0, 6, 0) - ret = av_find_stream_info(mFormatContext); -#else - ret = avformat_find_stream_info(mFormatContext, 0); -#endif + Debug(1, "Finding stream info"); + ret = avformat_find_stream_info(mFormatContext, nullptr); + if ( ret < 0 ) { Error("Unable to find stream info from %s due to: %s", mPath.c_str(), av_make_error_string(ret).c_str()); @@ -410,244 +324,191 @@ 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 continue; } 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 { Debug(2, "Have another audio stream."); } } } // end foreach stream - if ( mVideoStreamId == -1 ) { + + if (mVideoStreamId == -1) { Error("Unable to locate video stream in %s", mPath.c_str()); return -1; } Debug(3, "Found video stream at index %d, audio stream at index %d", mVideoStreamId, mAudioStreamId); - packetqueue = new zm_packetqueue( - (mVideoStreamId > mAudioStreamId) ? mVideoStreamId : mAudioStreamId); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - // mVideoCodecContext = avcodec_alloc_context3(NULL); - // avcodec_parameters_to_context(mVideoCodecContext, - // mFormatContext->streams[mVideoStreamId]->codecpar); - // this isn't copied. - // mVideoCodecContext->time_base = - // mFormatContext->streams[mVideoStreamId]->codec->time_base; -#else -#endif - mVideoCodecContext = mFormatContext->streams[mVideoStreamId]->codec; -#ifdef CODEC_FLAG2_FAST - mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG_LOW_DELAY; -#endif - - if ( mVideoCodecContext->codec_id == AV_CODEC_ID_H264 ) { - if ( (mVideoCodec = avcodec_find_decoder_by_name("h264_mmal")) == NULL ) { + AVCodec *mVideoCodec = 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(mVideoCodecContext->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; } } + mVideoCodecContext = avcodec_alloc_context3(mVideoCodec); + 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 ( 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) -// Print out available types + // 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)); } -#if LIBAVUTIL_VERSION_CHECK(56, 22, 0, 14, 0) + #if LIBAVUTIL_VERSION_CHECK(56, 22, 0, 14, 0) // Get hw_pix_fmt - for ( int i = 0;; i++ ) { + for (int i = 0;; i++) { const AVCodecHWConfig *config = avcodec_get_hw_config(mVideoCodec, i); - if ( !config ) { - Debug(1, "Decoder %s does not support device type %s.", - mVideoCodec->name, av_hwdevice_get_type_name(type)); + 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; - break; + Debug(1, "Decoder %s does support our type %s.", + mVideoCodec->name, av_hwdevice_get_type_name(type)); + //break; } else { - Debug(1, "decoder %s hwConfig doesn't match our type: %s, pix_fmt %s.", + Debug(1, "Decoder %s hwConfig doesn't match our type: %s != %s, pix_fmt %s.", mVideoCodec->name, + av_hwdevice_get_type_name(type), av_hwdevice_get_type_name(config->device_type), av_get_pix_fmt_name(config->pix_fmt) ); } } // end foreach hwconfig -#else + #else hw_pix_fmt = find_fmt_by_hw_type(type); -#endif - if ( hw_pix_fmt != AV_PIX_FMT_NONE ) { + #endif + if (hw_pix_fmt != AV_PIX_FMT_NONE) { Debug(1, "Selected hw_pix_fmt %d %s", hw_pix_fmt, av_get_pix_fmt_name(hw_pix_fmt)); - mVideoCodecContext->get_format = get_hw_format; + mVideoCodecContext->hwaccel_flags |= AV_HWACCEL_FLAG_IGNORE_LEVEL; + //if (!lavc_param->check_hw_profile) + mVideoCodecContext->hwaccel_flags |= AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH; ret = av_hwdevice_ctx_create(&hw_device_ctx, type, - (hwaccel_device != "" ? hwaccel_device.c_str(): NULL), NULL, 0); - if ( ret < 0 ) { - Error("Failed to create hwaccel device."); - return -1; + (hwaccel_device != "" ? hwaccel_device.c_str() : nullptr), nullptr, 0); + if ( ret < 0 and hwaccel_device != "" ) { + ret = av_hwdevice_ctx_create(&hw_device_ctx, type, nullptr, nullptr, 0); + } + if (ret < 0) { + Error("Failed to create hwaccel device. %s", av_make_error_string(ret).c_str()); + hw_pix_fmt = AV_PIX_FMT_NONE; + } else { + Debug(1, "Created hwdevice for %s", hwaccel_device.c_str()); + mVideoCodecContext->get_format = get_hw_format; + mVideoCodecContext->hw_device_ctx = av_buffer_ref(hw_device_ctx); } - Debug(1, "Created hwdevice for %s", hwaccel_device.c_str()); - mVideoCodecContext->hw_device_ctx = av_buffer_ref(hw_device_ctx); - hwFrame = zm_av_frame_alloc(); } else { - Debug(1, "Failed to setup hwaccel."); + Debug(1, "Failed to find suitable hw_pix_fmt."); } -#else + #else Debug(1, "AVCodec not new enough for hwaccel"); -#endif + #endif #else Warning("HWAccel support not compiled in."); #endif - } // end if hwacel_name + } // end if hwaccel_name + + // set codec to automatically determine how many threads suits best for the decoding job + mVideoCodecContext->thread_count = 0; + + if (mVideoCodec->capabilities | AV_CODEC_CAP_FRAME_THREADS) { + mVideoCodecContext->thread_type = FF_THREAD_FRAME; + } else if (mVideoCodec->capabilities | AV_CODEC_CAP_SLICE_THREADS) { + mVideoCodecContext->thread_type = FF_THREAD_SLICE; + } else { + mVideoCodecContext->thread_count = 1; //don't use multithreading + } - // Open the codec -#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) - ret = avcodec_open(mVideoCodecContext, mVideoCodec); -#else ret = avcodec_open2(mVideoCodecContext, mVideoCodec, &opts); -#endif - e = NULL; - while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL ) { + + e = 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); - Debug(1, hwFrame ? "HWACCEL in use" : "HWACCEL not in use"); + if (mAudioStreamId == -1 and !monitor->GetSecondPath().empty()) { + Debug(1, "Trying secondary stream at %s", monitor->GetSecondPath().c_str()); + FFmpeg_Input *second_input = new FFmpeg_Input(); + if (second_input->Open(monitor->GetSecondPath().c_str()) > 0) { + mSecondFormatContext = second_input->get_format_context(); + mAudioStreamId = second_input->get_audio_stream_id(); + mAudioStream = second_input->get_audio_stream(); + } else { + Warning("Failed to open secondary input"); + } + } // end if have audio stream if ( mAudioStreamId >= 0 ) { - 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 - )) == NULL ) { + AVCodec *mAudioCodec = 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, 0) < 0 ) { -#endif + if (avcodec_open2(mAudioCodecContext, mAudioCodec, nullptr) < 0) { Error("Unable to open codec for audio stream from %s", mPath.c_str()); return -1; - } - zm_dump_codec(mAudioCodecContext); - } // end if find decoder - } // end if have audio_context - - // Allocate space for the native video frame - mRawFrame = zm_av_frame_alloc(); - - // Allocate space for the converted video frame - mFrame = zm_av_frame_alloc(); - - if ( mRawFrame == NULL || mFrame == NULL ) { - Error("Unable to allocate frame for %s", mPath.c_str()); - return -1; - } - -#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 ) { - Error("Image size mismatch. Required: %d Available: %d", pSize, imagesize); - return -1; - } - -#if HAVE_LIBSWSCALE - if ( !sws_isSupportedInput(mVideoCodecContext->pix_fmt) ) { - Error("swscale does not support the codec format for input: %s", - av_get_pix_fmt_name(mVideoCodecContext->pix_fmt) - ); - return -1; - } - - if ( !sws_isSupportedOutput(imagePixFormat) ) { - Error("swscale does not support the target format: %s", - av_get_pix_fmt_name(imagePixFormat) - ); - return -1; - } - -# if 0 - // Must get a frame first to find out the actual format returned by decoding - mConvertContext = sws_getContext( - mVideoCodecContext->width, - mVideoCodecContext->height, - mVideoCodecContext->pix_fmt, - width, height, - imagePixFormat, SWS_BICUBIC, NULL, - NULL, NULL); - if ( mConvertContext == NULL ) { - Error("Unable to create conversion context for %s", mPath.c_str()); - return -1; - } -#endif -#else // HAVE_LIBSWSCALE - Fatal("You must compile ffmpeg with the --enable-swscale " - "option to use ffmpeg cameras"); -#endif // HAVE_LIBSWSCALE + } // end if opened + } // end if found decoder + } // end if mAudioStreamId if ( ((unsigned int)mVideoCodecContext->width != width) @@ -660,54 +521,21 @@ int FfmpegCamera::OpenFfmpeg() { mCanCapture = true; - return 0; -} // int FfmpegCamera::OpenFfmpeg() + return 1; +} // int FfmpegCamera::OpenFfmpeg() int FfmpegCamera::Close() { - Debug(2, "CloseFfmpeg called."); - mCanCapture = false; - if ( mFrame ) { - av_frame_free(&mFrame); - mFrame = NULL; - } - if ( mRawFrame ) { - av_frame_free(&mRawFrame); - mRawFrame = NULL; - } -#if HAVE_LIBAVUTIL_HWCONTEXT_H - if ( hwFrame ) { - av_frame_free(&hwFrame); - hwFrame = NULL; - } -#endif - -#if HAVE_LIBSWSCALE - if ( mConvertContext ) { - sws_freeContext(mConvertContext); - mConvertContext = NULL; - } -#endif - - if ( videoStore ) { - delete videoStore; - videoStore = NULL; - } - if ( mVideoCodecContext ) { avcodec_close(mVideoCodecContext); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - // avcodec_free_context(&mVideoCodecContext); -#endif - mVideoCodecContext = NULL; // Freed by av_close_input_file + avcodec_free_context(&mVideoCodecContext); + 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 = NULL; // Freed by av_close_input_file + mAudioCodecContext = nullptr; // Freed by av_close_input_file } #if HAVE_LIBAVUTIL_HWCONTEXT_H @@ -717,443 +545,25 @@ 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 = NULL; - } - - if ( packetqueue ) { - delete packetqueue; - packetqueue = NULL; + mFormatContext = nullptr; } return 0; } // end FfmpegCamera::Close -// Function to handle capture and store -int FfmpegCamera::CaptureAndRecord( - Image &image, - timeval recording, - char* event_file - ) { - if ( !mCanCapture ) { - return -1; - } - int ret; - - struct timeval video_buffer_duration = monitor->GetVideoBufferDuration(); - - int frameComplete = false; - while ( !frameComplete ) { - av_init_packet(&packet); - - ret = av_read_frame(mFormatContext, &packet); - if ( ret < 0 ) { - if ( - // Check if EOF. - ( - (ret == AVERROR_EOF) || - (mFormatContext->pb && mFormatContext->pb->eof_reached) - ) || - // Check for Connection failure. - (ret == -110) - ) { - Info("Unable to read packet from stream %d: error %d \"%s\".", - packet.stream_index, ret, av_make_error_string(ret).c_str()); - } else { - Error("Unable to read packet from stream %d: error %d \"%s\".", - packet.stream_index, ret, av_make_error_string(ret).c_str()); - } - return -1; - } - - if ( (packet.pts != AV_NOPTS_VALUE) && (packet.pts < -100000) ) { - // Ignore packets that have crazy negative pts. - // They aren't supposed to happen. - Warning("Ignore packet because pts %" PRId64 " is massively negative." - " Error count is %d", packet.pts, error_count); - dumpPacket( - mFormatContext->streams[packet.stream_index], - &packet, - "Ignored packet"); - if ( error_count > 100 ) { - Error("Bad packet count over 100, going to close and re-open stream"); - return -1; - } - error_count += 1; - continue; - } - // If we get a good frame, decrease the error count.. We could zero it... - if ( error_count > 0 ) error_count -= 1; - - int keyframe = packet.flags & AV_PKT_FLAG_KEY; - bytes += packet.size; - dumpPacket( - mFormatContext->streams[packet.stream_index], - &packet, - "Captured Packet"); - if ( packet.dts == AV_NOPTS_VALUE ) { - packet.dts = packet.pts; - } - - // Video recording - if ( recording.tv_sec ) { - uint32_t last_event_id = monitor->GetLastEventId(); - uint32_t video_writer_event_id = monitor->GetVideoWriterEventId(); - - if ( last_event_id != video_writer_event_id ) { - Debug(2, "Have change of event. last_event(%d), our current (%d)", - last_event_id, video_writer_event_id); - - if ( videoStore ) { - Info("Re-starting video storage module"); - - // I don't know if this is important or not... but I figure we might - // as well write this last packet out to the store before closing it. - // Also don't know how much it matters for audio. - if ( packet.stream_index == mVideoStreamId ) { - // Write the packet to our video store - int ret = videoStore->writeVideoFramePacket(&packet); - if ( ret < 0 ) { // Less than zero and we skipped a frame - Warning("Error writing last packet to videostore."); - } - } // end if video - - delete videoStore; - videoStore = NULL; - have_video_keyframe = false; - - monitor->SetVideoWriterEventId(0); - } // end if videoStore - } // end if end of recording - - if ( last_event_id && !videoStore ) { - // Instantiate the video storage module - - packetqueue->dumpQueue(); - if ( record_audio ) { - if ( mAudioStreamId == -1 ) { - Debug(3, "Record Audio on but no audio stream found"); - videoStore = new VideoStore((const char *) event_file, "mp4", - mFormatContext->streams[mVideoStreamId], - NULL, - this->getMonitor()); - - } else { - Debug(3, "Video module initiated with audio stream"); - videoStore = new VideoStore((const char *) event_file, "mp4", - mFormatContext->streams[mVideoStreamId], - mFormatContext->streams[mAudioStreamId], - this->getMonitor()); - } - } else { - if ( mAudioStreamId >= 0 ) { - Debug(3, "Record_audio is false so exclude audio stream"); - } - videoStore = new VideoStore((const char *) event_file, "mp4", - mFormatContext->streams[mVideoStreamId], - NULL, - this->getMonitor()); - } // end if record_audio - - if ( !videoStore->open() ) { - delete videoStore; - videoStore = NULL; - - } else { - monitor->SetVideoWriterEventId(last_event_id); - - // Need to write out all the frames from the last keyframe? - // No... need to write out all frames from when the event began. - // Due to PreEventFrames, this could be more than - // since the last keyframe. - unsigned int packet_count = 0; - ZMPacket *queued_packet; - struct timeval video_offset = {0}; - - // Clear all packets that predate the moment when the recording began - packetqueue->clear_unwanted_packets( - &recording, 0, mVideoStreamId); - - while ( (queued_packet = packetqueue->popPacket()) ) { - AVPacket *avp = queued_packet->av_packet(); - - // compute time offset between event start and first frame in video - if (packet_count == 0){ - monitor->SetVideoWriterStartTime(queued_packet->timestamp); - timersub(&queued_packet->timestamp, &recording, &video_offset); - Info("Event video offset is %.3f sec (<0 means video starts early)", - video_offset.tv_sec + video_offset.tv_usec*1e-6); - } - - packet_count += 1; - // Write the packet to our video store - Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", - avp->stream_index, - avp->flags & AV_PKT_FLAG_KEY, - packetqueue->size()); - if ( avp->stream_index == mVideoStreamId ) { - ret = videoStore->writeVideoFramePacket(avp); - have_video_keyframe = true; - } else if ( avp->stream_index == mAudioStreamId ) { - ret = videoStore->writeAudioFramePacket(avp); - } else { - Warning("Unknown stream id in queued packet (%d)", - avp->stream_index); - ret = -1; - } - if ( ret < 0 ) { - // Less than zero and we skipped a frame - } - delete queued_packet; - } // end while packets in the packetqueue - Debug(2, "Wrote %d queued packets", packet_count); - } - } // end if ! was recording - - } else { - // Not recording - - if ( videoStore ) { - Debug(1, "Deleting videoStore instance"); - delete videoStore; - videoStore = NULL; - have_video_keyframe = false; - monitor->SetVideoWriterEventId(0); - } - } // end if recording or not - - // Buffer video packets, since we are not recording. - // All audio packets are keyframes, so only if it's a video keyframe - if ( packet.stream_index == mVideoStreamId ) { - if ( keyframe ) { - Debug(3, "Clearing queue"); - if (video_buffer_duration.tv_sec > 0 || video_buffer_duration.tv_usec > 0) { - packetqueue->clearQueue(&video_buffer_duration, mVideoStreamId); - } - else { - packetqueue->clearQueue(monitor->GetPreEventCount(), mVideoStreamId); - } - - if ( - packetqueue->packet_count(mVideoStreamId) - >= - monitor->GetImageBufferCount() - ) { - Warning( - "ImageBufferCount %d is too small. " - "Needs to be at least %d. " - "Either increase it or decrease time between keyframes", - monitor->GetImageBufferCount(), - packetqueue->packet_count(mVideoStreamId)+1); - } - - packetqueue->queuePacket(&packet); - } else if ( packetqueue->size() ) { - // it's a keyframe or we already have something in the queue - packetqueue->queuePacket(&packet); - } - } else if ( packet.stream_index == mAudioStreamId ) { - // Ensure that the queue always begins with a video keyframe - if ( record_audio && packetqueue->size() ) { - packetqueue->queuePacket(&packet); - } - } // end if packet type - - if ( packet.stream_index == mVideoStreamId ) { - if ( (have_video_keyframe || keyframe) && videoStore ) { - int ret = videoStore->writeVideoFramePacket(&packet); - if ( ret < 0 ) { - // Less than zero and we skipped a frame - Error("Unable to write video packet code: %d, framecount %d: %s", - ret, frameCount, av_make_error_string(ret).c_str()); - } else { - have_video_keyframe = true; - } - } // end if keyframe or have_video_keyframe - - ret = zm_send_packet_receive_frame(mVideoCodecContext, mRawFrame, packet); - if ( ret < 0 ) { - if ( AVERROR(EAGAIN) != ret ) { - Warning("Unable to receive frame %d: code %d %s. error count is %d", - frameCount, ret, av_make_error_string(ret).c_str(), error_count); - error_count += 1; - if ( error_count > 100 ) { - Error("Error count over 100, going to close and re-open stream"); - return -1; - } - } - zm_av_packet_unref(&packet); - continue; - } - if ( error_count > 0 ) error_count--; - zm_dump_video_frame(mRawFrame, "raw frame from decoder"); -#if HAVE_LIBAVUTIL_HWCONTEXT_H -#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) - if ( - (hw_pix_fmt != AV_PIX_FMT_NONE) - && - (mRawFrame->format == hw_pix_fmt) - ) { - /* retrieve data from GPU to CPU */ - ret = av_hwframe_transfer_data(hwFrame, mRawFrame, 0); - if ( ret < 0 ) { - Error("Unable to transfer frame at frame %d: %s, continuing", - frameCount, av_make_error_string(ret).c_str()); - zm_av_packet_unref(&packet); - continue; - } - zm_dump_video_frame(hwFrame, "After hwtransfer"); - - hwFrame->pts = mRawFrame->pts; - input_frame = hwFrame; - } else { -#endif -#endif - input_frame = mRawFrame; -#if HAVE_LIBAVUTIL_HWCONTEXT_H -#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) - } -#endif -#endif - if ( transfer_to_image(image, mFrame, input_frame) < 0 ) { - Error("Failed to transfer from frame to image"); - zm_av_packet_unref(&packet); - return -1; - } - - frameComplete = 1; - frameCount++; - } else if ( packet.stream_index == mAudioStreamId ) { - // FIXME best way to copy all other streams - frameComplete = 1; - if ( videoStore ) { - if ( record_audio ) { - if ( have_video_keyframe ) { - // Write the packet to our video store - // FIXME no relevance of last key frame - int ret = videoStore->writeAudioFramePacket(&packet); - if ( ret < 0 ) { - // Less than zero and we skipped a frame - Warning("Failure to write audio packet."); - zm_av_packet_unref(&packet); - return 0; - } - } else { - Debug(3, "Not recording audio because no video keyframe"); - } - } else { - Debug(4, "Not doing recording of audio packet"); - } - } else { - Debug(4, "Have audio packet, but not recording atm"); - } - zm_av_packet_unref(&packet); - return 0; - } else { -#if LIBAVUTIL_VERSION_CHECK(56, 23, 0, 23, 0) - Debug(3, "Some other stream index %d, %s", - packet.stream_index, - av_get_media_type_string( - mFormatContext->streams[packet.stream_index]->codecpar->codec_type) - ); -#else - Debug(3, "Some other stream index %d", packet.stream_index); -#endif - } // end if is video or audio or something else - - // the packet contents are ref counted... when queuing, we allocate another - // packet and reference it with that one, so we should always need to unref - // here, which should not affect the queued version. - zm_av_packet_unref(&packet); - } // end while ! frameComplete - return frameCount; -} // end FfmpegCamera::CaptureAndRecord - -int FfmpegCamera::transfer_to_image( - Image &image, - AVFrame *output_frame, - AVFrame *input_frame - ) { - uint8_t* directbuffer; - - /* Request a writeable buffer of the target image */ - directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); - if ( directbuffer == NULL ) { - Error("Failed requesting writeable buffer for the captured image."); - return -1; - } -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - // From what I've read, we should align the linesizes to 32bit so that ffmpeg can use SIMD instructions too. - int size = av_image_fill_arrays( - output_frame->data, output_frame->linesize, - directbuffer, imagePixFormat, width, height, 32); - if ( size < 0 ) { - Error("Problem setting up data pointers into image %s", - av_make_error_string(size).c_str()); - } -#else - avpicture_fill((AVPicture *)output_frame, directbuffer, - imagePixFormat, width, height); -#endif -#if HAVE_LIBSWSCALE - if ( !mConvertContext ) { - mConvertContext = sws_getContext( - input_frame->width, - input_frame->height, - (AVPixelFormat)input_frame->format, - width, height, - imagePixFormat, SWS_BICUBIC, NULL, - NULL, NULL); - if ( mConvertContext == NULL ) { - Error("Unable to create conversion context for %s from %s to %s", - mPath.c_str(), - av_get_pix_fmt_name((AVPixelFormat)input_frame->format), - av_get_pix_fmt_name(imagePixFormat) - ); - return -1; - } - Debug(1, "Setup conversion context for %dx%d %s to %dx%d %s", - input_frame->width, input_frame->height, - av_get_pix_fmt_name((AVPixelFormat)input_frame->format), - width, height, - av_get_pix_fmt_name(imagePixFormat) - ); - } - - int ret = - sws_scale( - mConvertContext, input_frame->data, input_frame->linesize, - 0, mVideoCodecContext->height, - output_frame->data, output_frame->linesize); - if ( ret < 0 ) { - Error("Unable to convert format %u %s linesize %d height %d to format %u %s linesize %d at frame %d codec %u %s lines %d: code: %d", - input_frame->format, av_get_pix_fmt_name((AVPixelFormat)input_frame->format), - input_frame->linesize, mVideoCodecContext->height, - imagePixFormat, - av_get_pix_fmt_name(imagePixFormat), - output_frame->linesize, - frameCount, - mVideoCodecContext->pix_fmt, av_get_pix_fmt_name(mVideoCodecContext->pix_fmt), - mVideoCodecContext->height, - ret - ); - return -1; - } -#else // HAVE_LIBSWSCALE - Fatal("You must compile ffmpeg with the --enable-swscale " - "option to use ffmpeg cameras"); -#endif // HAVE_LIBSWSCALE - return 0; -} // end int FfmpegCamera::transfer_to_image - 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 7a6439ee4..e320d3b31 100644 --- a/src/zm_ffmpeg_camera.h +++ b/src/zm_ffmpeg_camera.h @@ -22,10 +22,7 @@ #include "zm_camera.h" -#include "zm_buffer.h" -#include "zm_ffmpeg.h" -#include "zm_videostore.h" -#include "zm_packetqueue.h" +#include #if HAVE_LIBAVUTIL_HWCONTEXT_H typedef struct DecodeContext { @@ -39,29 +36,21 @@ typedef struct DecodeContext { class FfmpegCamera : public Camera { protected: std::string mPath; + std::string mSecondPath; std::string mMethod; std::string mOptions; + + std::string encoder_options; std::string hwaccel_name; std::string hwaccel_device; int frameCount; -#if HAVE_LIBAVFORMAT - AVFormatContext *mFormatContext; - int mVideoStreamId; - int mAudioStreamId; - AVCodecContext *mVideoCodecContext; - AVCodecContext *mAudioCodecContext; - AVCodec *mVideoCodec; - AVCodec *mAudioCodec; - AVFrame *mRawFrame; - AVFrame *mFrame; _AVPIXELFORMAT imagePixFormat; - AVFrame *input_frame; // Use to point to mRawFrame or hwFrame; - AVFrame *hwFrame; // Will also be used to indicate if hwaccel is in use + bool use_hwaccel; //will default to on if hwaccel specified, will get turned off if there is a failure #if HAVE_LIBAVUTIL_HWCONTEXT_H - AVBufferRef *hw_device_ctx = NULL; + AVBufferRef *hw_device_ctx = nullptr; #endif // Used to store the incoming packet, it will get copied when queued. @@ -70,24 +59,18 @@ class FfmpegCamera : public Camera { AVPacket packet; int OpenFfmpeg(); - int Close(); + int Close() override; bool mCanCapture; -#endif // HAVE_LIBAVFORMAT - VideoStore *videoStore; - zm_packetqueue *packetqueue; - bool have_video_keyframe; - -#if HAVE_LIBSWSCALE struct SwsContext *mConvertContext; -#endif int error_count; public: FfmpegCamera( - int p_id, + const Monitor *monitor, const std::string &path, + const std::string &second_path, const std::string &p_method, const std::string &p_options, int p_width, @@ -108,16 +91,11 @@ class FfmpegCamera : public Camera { const std::string &Options() const { return mOptions; } const std::string &Method() const { return mMethod; } - void Initialise(); - void Terminate(); - - int PrimeCapture(); - int PreCapture(); - int Capture( Image &image ); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ); - int PostCapture(); + int PrimeCapture() override; + int PreCapture() override; + int Capture(std::shared_ptr &p) override; + int PostCapture() override; private: static int FfmpegInterruptCallback(void*ctx); - int transfer_to_image(Image &i, AVFrame *output_frame, AVFrame *input_frame); }; #endif // ZM_FFMPEG_CAMERA_H diff --git a/src/zm_ffmpeg_input.cpp b/src/zm_ffmpeg_input.cpp index 352de347c..8e0a5b0e8 100644 --- a/src/zm_ffmpeg_input.cpp +++ b/src/zm_ffmpeg_input.cpp @@ -1,23 +1,44 @@ - #include "zm_ffmpeg_input.h" -#include "zm_logger.h" + #include "zm_ffmpeg.h" +#include "zm_logger.h" FFmpeg_Input::FFmpeg_Input() { - input_format_context = NULL; + input_format_context = nullptr; video_stream_id = -1; audio_stream_id = -1; FFMPEGInit(); - streams = NULL; - frame = NULL; + streams = nullptr; + frame = nullptr; last_seek_request = -1; } FFmpeg_Input::~FFmpeg_Input() { - if ( streams ) { - delete streams; - streams = NULL; + if ( input_format_context ) { + Close(); } + if ( frame ) { + av_frame_free(&frame); + frame = nullptr; + } +} // end ~FFmpeg_Input() + +/* Takes streams provided from elsewhere. They might not come from the same source + * but we will treat them as if they are. */ +int FFmpeg_Input::Open( + const AVStream * video_in_stream, + const AVCodecContext * video_in_ctx, + const AVStream * audio_in_stream, + const AVCodecContext * audio_in_ctx + ) { + int max_stream_index = video_stream_id = video_in_stream->index; + + if ( audio_in_stream ) { + max_stream_index = video_in_stream->index > audio_in_stream->index ? video_in_stream->index : audio_in_stream->index; + audio_stream_id = audio_in_stream->index; + } + streams = new stream[max_stream_index+1]; + return 1; } int FFmpeg_Input::Open(const char *filepath) { @@ -25,16 +46,16 @@ int FFmpeg_Input::Open(const char *filepath) { int error; /** Open the input file to read from it. */ - error = avformat_open_input(&input_format_context, filepath, NULL, NULL); + error = avformat_open_input(&input_format_context, filepath, nullptr, nullptr); if ( error < 0 ) { - Error("Could not open input file '%s' (error '%s')\n", - filepath, av_make_error_string(error).c_str() ); - input_format_context = NULL; + Error("Could not open input file '%s' (error '%s')", + filepath, av_make_error_string(error).c_str()); + input_format_context = nullptr; return error; } /** Get information on the input file (number of streams etc.). */ - if ( (error = avformat_find_stream_info(input_format_context, NULL)) < 0 ) { + if ( (error = avformat_find_stream_info(input_format_context, nullptr)) < 0 ) { Error( "Could not open find stream info (error '%s')", av_make_error_string(error).c_str() @@ -67,12 +88,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(NULL); + 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"); @@ -82,28 +99,44 @@ int FFmpeg_Input::Open(const char *filepath) { Debug(1, "Using codec (%s) for stream %d", streams[i].codec->name, i); } - error = avcodec_open2(streams[i].context, streams[i].codec, NULL); + error = avcodec_open2(streams[i].context, streams[i].codec, nullptr); 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; } } // 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 ) -AVFrame *FFmpeg_Input::get_frame(int stream_id) { +int FFmpeg_Input::Close( ) { + if ( streams ) { + for ( unsigned int i = 0; i < input_format_context->nb_streams; i += 1 ) { + avcodec_close(streams[i].context); + avcodec_free_context(&streams[i].context); + streams[i].context = nullptr; + } + delete[] streams; + streams = nullptr; + } + if ( input_format_context ) { + avformat_close_input(&input_format_context); + input_format_context = nullptr; + } + return 1; +} // end int FFmpeg_Input::Close() + +AVFrame *FFmpeg_Input::get_frame(int stream_id) { int frameComplete = false; AVPacket packet; av_init_packet(&packet); @@ -118,45 +151,49 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id) { (ret == -110) ) { Info("av_read_frame returned %s.", av_make_error_string(ret).c_str()); - return NULL; + return nullptr; } Error("Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, ret, av_make_error_string(ret).c_str()); - return NULL; + return nullptr; } - dumpPacket(input_format_context->streams[packet.stream_index], &packet, "Received packet"); + ZM_DUMP_STREAM_PACKET(input_format_context->streams[packet.stream_index], packet, "Received packet"); - if ( (stream_id < 0) || (packet.stream_index == stream_id) ) { - Debug(3, "Packet is for our stream (%d)", packet.stream_index); + if ( (stream_id >= 0) && (packet.stream_index != stream_id) ) { + Debug(1,"Packet is not for our stream (%d)", packet.stream_index ); + continue; + } - AVCodecContext *context = streams[packet.stream_index].context; + AVCodecContext *context = streams[packet.stream_index].context; - if ( frame ) { - av_frame_free(&frame); - frame = zm_av_frame_alloc(); + if ( frame ) { + av_frame_free(&frame); + frame = zm_av_frame_alloc(); + } else { + frame = zm_av_frame_alloc(); + } + ret = zm_send_packet_receive_frame(context, frame, packet); + if ( ret < 0 ) { + Error("Unable to decode frame at frame %d: %d %s, continuing", + streams[packet.stream_index].frame_count, ret, av_make_error_string(ret).c_str()); + zm_av_packet_unref(&packet); + av_frame_free(&frame); + continue; + } else { + if ( is_video_stream(input_format_context->streams[packet.stream_index]) ) { + zm_dump_video_frame(frame, "resulting video frame"); } else { - frame = zm_av_frame_alloc(); - } - ret = zm_send_packet_receive_frame(context, frame, packet); - if ( ret < 0 ) { - Error("Unable to decode frame at frame %d: %s, continuing", - streams[packet.stream_index].frame_count, av_make_error_string(ret).c_str()); - zm_av_packet_unref(&packet); - av_frame_free(&frame); - continue; - } else { - zm_dump_frame(frame, "resulting frame"); + zm_dump_frame(frame, "resulting frame"); } + } - frameComplete = 1; - } // end if it's the right stream + frameComplete = 1; zm_av_packet_unref(&packet); - } // end while ! frameComplete + } // end while !frameComplete return frame; - -} // end AVFrame *FFmpeg_Input::get_frame +} // end AVFrame *FFmpeg_Input::get_frame AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) { Debug(1, "Getting frame from stream %d at %f", stream_id, at); @@ -168,54 +205,67 @@ 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 NULL; + 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 NULL; - } + if (!frame) { + Warning("Unable to get frame."); + return nullptr; + } + } // end if ! frame if ( (last_seek_request >= 0) && - (last_seek_request > seek_target ) + (last_seek_request > seek_target) && (frame->pts > seek_target) ) { zm_dump_frame(frame, "frame->pts > seek_target, seek backwards"); // our frame must be beyond our seek target. so go backwards to before it - if ( ( ret = av_seek_frame(input_format_context, stream_id, seek_target, + if (( ret = av_seek_frame(input_format_context, stream_id, seek_target, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME - ) < 0 ) ) { - Error("Unable to seek in stream"); - return NULL; + ) ) < 0) { + Error("Unable to seek in stream %d", ret); + return nullptr; } // Have to grab a frame to update our current frame to know where we are get_frame(stream_id); - zm_dump_frame(frame, "frame->pts > seek_target, got"); + if ( is_video_stream(input_format_context->streams[stream_id]) ) { + zm_dump_video_frame(frame, "frame->pts > seek_target, got"); + } else { + zm_dump_frame(frame, "frame->pts > seek_target, got"); + } + } else if ( last_seek_request == seek_target ) { + // paused case, sending keepalives + return frame; } // end if frame->pts > seek_target last_seek_request = seek_target; // Seeking seems to typically seek to a keyframe, so then we have to decode until we get the frame we want. - if ( frame->pts <= seek_target ) { - zm_dump_frame(frame, "pts <= seek_target"); - while ( frame && (frame->pts < seek_target) ) { - if ( !get_frame(stream_id) ) - return frame; + if ( frame->pts <= seek_target ) { + if ( is_video_stream(input_format_context->streams[stream_id]) ) { + zm_dump_video_frame(frame, "pts <= seek_target"); + } else { + zm_dump_frame(frame, "pts <= seek_target"); } + while ( frame && (frame->pts < seek_target) ) { + if ( !get_frame(stream_id) ) { + Warning("Got no frame. returning nothing"); + return frame; + } + } + zm_dump_frame(frame, "frame->pts <= seek_target, got"); return frame; } 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 900f14d4a..6ccc87682 100644 --- a/src/zm_ffmpeg_input.h +++ b/src/zm_ffmpeg_input.h @@ -1,17 +1,13 @@ #ifndef ZM_FFMPEG_INPUT_H #define ZM_FFMPEG_INPUT_H -#ifdef __cplusplus +#include "zm_define.h" + extern "C" { -#endif - -#include "libavformat/avformat.h" -#include "libavformat/avio.h" -#include "libavcodec/avcodec.h" - -#ifdef __cplusplus +#include +#include +#include } -#endif class FFmpeg_Input { @@ -19,16 +15,28 @@ class FFmpeg_Input { FFmpeg_Input(); ~FFmpeg_Input(); - int Open( const char *filename ); + int Open(const char *filename ); + int Open( + const AVStream *, + const AVCodecContext *, + const AVStream *, + const AVCodecContext *); int Close(); - AVFrame *get_frame( int stream_id=-1 ); - AVFrame *get_frame( int stream_id, double at ); - int get_video_stream_id() { + AVFrame *get_frame(int stream_id=-1); + AVFrame *get_frame(int stream_id, double at); + int get_video_stream_id() const { return video_stream_id; } - int get_audio_stream_id() { + int get_audio_stream_id() const { return audio_stream_id; } + AVStream *get_video_stream() { + return ( video_stream_id >= 0 ) ? input_format_context->streams[video_stream_id] : nullptr; + } + AVStream *get_audio_stream() { + return ( audio_stream_id >= 0 ) ? input_format_context->streams[audio_stream_id] : nullptr; + } + AVFormatContext *get_format_context() { return input_format_context; }; private: typedef struct { diff --git a/src/zm_ffmpeg_output.cpp b/src/zm_ffmpeg_output.cpp new file mode 100644 index 000000000..ec677f265 --- /dev/null +++ b/src/zm_ffmpeg_output.cpp @@ -0,0 +1,163 @@ + +#include "zm_ffmpeg_input.h" +#include "zm_logger.h" +#include "zm_ffmpeg.h" + +FFmpeg_Output::FFmpeg_Output() { + input_format_context = NULL; + video_stream_id = -1; + audio_stream_id = -1; + + FFMPEGInit(); +} +FFmpeg_Output::~FFmpeg_Output() { +} + +int FFmpeg_Output::Open( const char *filepath ) { + + int error; + + /** Open the input file to read from it. */ + if ( (error = avformat_open_input( &input_format_context, filepath, NULL, NULL)) < 0 ) { + + Error("Could not open input file '%s' (error '%s')\n", + filepath, av_make_error_string(error).c_str() ); + input_format_context = NULL; + return error; + } + + /** Get information on the input file (number of streams etc.). */ + if ( (error = avformat_find_stream_info(input_format_context, NULL)) < 0 ) { + Error( "Could not open find stream info (error '%s')\n", + av_make_error_string(error).c_str() ); + avformat_close_input(&input_format_context); + return error; + } + + for ( unsigned int i = 0; i < input_format_context->nb_streams; i += 1 ) { + if ( is_video_stream( input_format_context->streams[i] ) ) { + zm_dump_stream_format(input_format_context, i, 0, 0); + if ( video_stream_id == -1 ) { + video_stream_id = i; + // if we break, then we won't find the audio stream + } else { + Warning( "Have another video stream." ); + } + } else if ( is_audio_stream( input_format_context->streams[i] ) ) { + if ( audio_stream_id == -1 ) { + audio_stream_id = i; + } else { + Warning( "Have another audio stream." ); + } + } + + streams[i].frame_count = 0; + 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"); + avformat_close_input(&input_format_context); + return AVERROR_EXIT; + } else { + Debug(1, "Using codec (%s) for stream %d", streams[i].codec->name, i ); + } + + if ((error = avcodec_open2( streams[i].context, streams[i].codec, NULL)) < 0) { + Error( "Could not open input codec (error '%s')\n", + av_make_error_string(error).c_str() ); + avcodec_free_context( &streams[i].context ); + avformat_close_input(&input_format_context); + return error; + } + } // end foreach stream + + if ( video_stream_id == -1 ) + Error( "Unable to locate video stream in %s", filepath ); + if ( audio_stream_id == -1 ) + Debug( 3, "Unable to locate audio stream in %s", filepath ); + + return 0; +} // end int FFmpeg_Output::Open( const char * filepath ) + +AVFrame *FFmpeg_Output::get_frame( int stream_id ) { + Debug(1, "Getting frame from stream %d", stream_id ); + + int frameComplete = false; + AVPacket packet; + av_init_packet( &packet ); + AVFrame *frame = zm_av_frame_alloc(); + char errbuf[AV_ERROR_MAX_STRING_SIZE]; + + while ( !frameComplete ) { + int ret = av_read_frame( input_format_context, &packet ); + if ( ret < 0 ) { + av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); + if ( + // Check if EOF. + (ret == AVERROR_EOF || (input_format_context->pb && input_format_context->pb->eof_reached)) || + // Check for Connection failure. + (ret == -110) + ) { + Info( "av_read_frame returned %s.", errbuf ); + return NULL; + } + Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, ret, errbuf ); + return NULL; + } + + if ( (stream_id < 0 ) || ( packet.stream_index == stream_id ) ) { + Debug(1,"Packet is for our stream (%d)", packet.stream_index ); + + AVCodecContext *context = streams[packet.stream_index].context; + + ret = avcodec_send_packet( context, &packet ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to send packet at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } else { + Debug(1, "Success getting a packet"); + } + +#if HAVE_AVUTIL_HWCONTEXT_H + if ( hwaccel ) { + ret = avcodec_receive_frame( context, hwFrame ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to receive frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } + ret = av_hwframe_transfer_data(frame, hwFrame, 0); + if (ret < 0) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to transfer frame at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } + } else { +#endif + Debug(1,"Getting a frame?"); + ret = avcodec_receive_frame( context, frame ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to send packet at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } + +#if HAVE_AVUTIL_HWCONTEXT_H + } +#endif + + frameComplete = 1; + } // end if it's the right stream + + zm_av_packet_unref( &packet ); + + } // end while ! frameComplete + return frame; + +} // end AVFrame *FFmpeg_Output::get_frame diff --git a/src/zm_ffmpeg_output.h b/src/zm_ffmpeg_output.h new file mode 100644 index 000000000..9ab4ea403 --- /dev/null +++ b/src/zm_ffmpeg_output.h @@ -0,0 +1,40 @@ +#ifndef ZM_FFMPEG_INPUT_H +#define ZM_FFMPEG_INPUT_H + +extern "C" { +#include +#include +#include +} + +class FFmpeg_Output { + + public: + FFmpeg_Output(); + ~FFmpeg_Output(); + + int Open( const char *filename ); + int Close(); + AVFrame *put_frame( int stream_id=-1 ); + AVFrame *put_packet( int stream_id=-1 ); + int get_video_stream_id() { + return video_stream_id; + } + int get_audio_stream_id() { + return audio_stream_id; + } + + private: + typedef struct { + AVCodecContext *context; + AVCodec *codec; + int frame_count; + } stream; + + stream streams[2]; + int video_stream_id; + int audio_stream_id; + AVFormatContext *input_format_context; +}; + +#endif diff --git a/src/zm_fifo.cpp b/src/zm_fifo.cpp index 0c0f836bd..a999adc23 100644 --- a/src/zm_fifo.cpp +++ b/src/zm_fifo.cpp @@ -17,247 +17,166 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // +#include "zm_fifo.h" + +#include "zm_monitor.h" +#include "zm_signal.h" #include #include -#include -#include -#include +#include +#include -#include "zm.h" -#include "zm_time.h" -#include "zm_signal.h" -#include "zm_monitor.h" -#include "zm_fifo.h" #define RAW_BUFFER 512 -static bool zm_fifodbg_inited = false; -FILE *zm_fifodbg_log_fd = 0; -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 = NULL; - 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 == NULL ) { - 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/%d/dbgpipe.log", - monitor->getStorage()->Path(), monitor->Id()); - zmFifoDbgOpen(); - return 1; -} - -void zmFifoDbgOutput( - int hex, - const char * const file, - const int line, - const int level, - const char *fstring, - ... - ) { - char dbg_string[8192]; - int str_size = sizeof(dbg_string); - - va_list arg_ptr; - if ( (!zm_fifodbg_inited) || ( !zm_fifodbg_log_fd && !zmFifoDbgOpen() ) ) - return; - - char *dbg_ptr = dbg_string; - va_start(arg_ptr, fstring); - if ( hex ) { - unsigned char *data = va_arg(arg_ptr, unsigned char *); - int len = va_arg(arg_ptr, int); - dbg_ptr += snprintf(dbg_ptr, str_size-(dbg_ptr-dbg_string), "%d:", len); - for ( int i = 0; i < len; i++ ) { - dbg_ptr += snprintf(dbg_ptr, str_size-(dbg_ptr-dbg_string), " %02x", data[i]); - } - } else { - dbg_ptr += vsnprintf(dbg_ptr, str_size-(dbg_ptr-dbg_string), fstring, arg_ptr); - } - va_end(arg_ptr); - strncpy(dbg_ptr++, "\n", 2); - int res = fwrite(dbg_string, dbg_ptr-dbg_string, 1, zm_fifodbg_log_fd); - if ( res != 1 ) { - fclose(zm_fifodbg_log_fd); - zm_fifodbg_log_fd = NULL; - } else { - fflush(zm_fifodbg_log_fd); - } -} - -bool FifoStream::sendRAWFrames() { - static unsigned char buffer[RAW_BUFFER]; - int fd = open(stream_path, O_RDONLY); - if ( fd < 0 ) { - Error("Can't open %s: %s", stream_path, strerror(errno)); - return false; - } - while ( (bytes_read = read(fd, buffer, RAW_BUFFER)) ) { - if ( bytes_read == 0 ) - continue; - if ( bytes_read < 0 ) { - Error("Problem during reading: %s", strerror(errno)); - close(fd); - return false; - } - if ( fwrite(buffer, bytes_read, 1, stdout) != 1 ) { - Error("Problem during writing: %s", strerror(errno)); - close(fd); - return false; - } - fflush(stdout); - } - close(fd); - return true; -} - -void FifoStream::file_create_if_missing( - 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, - "--ZoneMinderFrame\r\n" - "Content-Type: image/jpeg\r\n" - "Content-Length: %d\r\n\r\n", - total_read) < 0 ) { - Error("Problem during writing: %s", strerror(errno)); - return false; +#ifdef __linux__ + int ret = fcntl(raw_fd, F_SETPIPE_SZ, PIPE_SIZE); + if (ret < 0) { + Error("set pipe size failed."); } - - if ( fwrite(buffer, total_read, 1, stdout) != 1 ) { - Error("Problem during reading: %s", strerror(errno)); - return false; + long pipe_size = (long)fcntl(raw_fd, F_GETPIPE_SZ); + if (pipe_size == -1) { + Error("get pipe size failed."); } - fprintf(stdout, "\r\n\r\n"); - fflush(stdout); - last_frame_sent = TV_2_FLOAT(now); - frame_count++; + Debug(1, "default pipe size: %ld\n", pipe_size); +#endif return true; } -void FifoStream::setStreamStart(const char * path) { - stream_path = strdup(path); +bool Fifo::close() { + if (outfile) { + fclose(outfile); + } + + return true; } -void FifoStream::setStreamStart(int monitor_id, const char * format) { - char diag_path[PATH_MAX]; - const char * filename; - Monitor * monitor = Monitor::Load(monitor_id, false, Monitor::QUERY); +bool Fifo::writePacket(const ZMPacket &packet) { + if (!(outfile or open())) return false; - if ( !strcmp(format, "reference") ) { - stream_type = MJPEG; - filename = "diagpipe-r.jpg"; - } else if ( !strcmp(format, "delta") ) { - filename = "diagpipe-d.jpg"; - stream_type = MJPEG; - } else { - stream_type = RAW; - filename = "dbgpipe.log"; - } - - snprintf(diag_path, sizeof(diag_path), "%s/%d/%s", - monitor->getStorage()->Path(), monitor->Id(), filename); - setStreamStart(diag_path); -} - -void FifoStream::runStream() { - if ( stream_type == MJPEG ) { - fprintf(stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n"); - } else { - fprintf(stdout, "Content-Type: text/html\r\n\r\n"); - } - - char lock_file[PATH_MAX]; - snprintf(lock_file, sizeof(lock_file), "%s.rlock", stream_path); - file_create_if_missing(lock_file, false); - - int fd_lock = open(lock_file, O_RDONLY); - if ( fd_lock < 0 ) { - Error("Can't open %s: %s", lock_file, strerror(errno)); - return; - } - int res = flock(fd_lock, LOCK_EX | LOCK_NB); - if ( res < 0 ) { - Error("Flocking problem on %s: - %s", lock_file, strerror(errno)); - close(fd_lock); - return; - } - - while ( !zm_terminate ) { - gettimeofday(&now, NULL); - 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 065fd569c..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 @@ -19,68 +19,42 @@ #ifndef ZM_FIFO_H #define ZM_FIFO_H -#if 0 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "zm.h" -#include "zm_image.h" -#endif -#include "zm_monitor.h" #include "zm_stream.h" +#include "zm_packet.h" -#define zmFifoDbgPrintf(level, params...) {\ - zmFifoDbgOutput(0, __FILE__, __LINE__, level, ##params);\ - } +class Monitor; -#ifndef ZM_DBG_OFF -#define FifoDebug(level, params...) zmFifoDbgPrintf(level, ##params) -#else -#define FifoDebug(level, params...) -#endif -void zmFifoDbgOutput( - int hex, - const char * const file, - const int line, - const int level, - const char *fstring, - ...) __attribute__((format(printf, 5, 6))); -int zmFifoDbgInit(Monitor * monitor); - -class FifoStream : public StreamBase { +class Fifo { private: - char * stream_path; - int fd; - int total_read; - int bytes_read; - unsigned int frame_count; - static void file_create_if_missing( - const char * path, - bool is_fifo, - bool delete_fake_fifo = true - ); - - protected: - typedef enum { MJPEG, RAW } StreamType; - StreamType stream_type; - bool sendMJEGFrames(); - bool sendRAWFrames(); - void processCommand(const CmdMsg *msg) {} + std::string path; + bool on_blocking_abort; + FILE *outfile; + int raw_fd; public: - FifoStream() {} - static void fifo_create_if_missing( - const char * path, - bool delete_fake_fifo = true); - void setStreamStart(const char * path); - void setStreamStart(int monitor_id, const char * format); - void runStream(); + static 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) + {} + 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_threaddata.cpp b/src/zm_fifo_debug.h similarity index 53% rename from src/zm_threaddata.cpp rename to src/zm_fifo_debug.h index 6b25d5714..08f4c4fb5 100644 --- a/src/zm_threaddata.cpp +++ b/src/zm_fifo_debug.h @@ -1,21 +1,42 @@ // -// ZoneMinder Explicit Thread Template Class Instantiations, $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 -template class ThreadData; -template class ThreadData; +class Monitor; + +#define zmFifoDbgPrintf(level, params...) {\ + zmFifoDbgOutput(0, __FILE__, __LINE__, level, ##params);\ + } + +#ifndef ZM_DBG_OFF +#define FifoDebug(level, params...) zmFifoDbgPrintf(level, ##params) +#else +#define FifoDebug(level, params...) +#endif +void zmFifoDbgOutput( + int hex, + const char * const file, + const int line, + const int level, + const char *fstring, + ...) __attribute__((format(printf, 5, 6))); +int zmFifoDbgInit(Monitor * monitor); + +#endif // ZM_FIFO_DEBUG_H diff --git a/src/zm_fifo_stream.cpp b/src/zm_fifo_stream.cpp new file mode 100644 index 000000000..0acb720d2 --- /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::steady_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..332275771 --- /dev/null +++ b/src/zm_fifo_stream.h @@ -0,0 +1,51 @@ +// +// 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; + + protected: + typedef enum { UNKNOWN, MJPEG, RAW } StreamType; + StreamType stream_type; + bool sendMJEGFrames(); + bool sendRAWFrames(); + void processCommand(const CmdMsg *msg) override {} + + public: + FifoStream() : + StreamBase(), + total_read(0), + bytes_read(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 a7883c5d5..feaad3fe0 100644 --- a/src/zm_file_camera.cpp +++ b/src/zm_file_camera.cpp @@ -17,21 +17,13 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "zm.h" #include "zm_file_camera.h" +#include "zm_packet.h" +#include + FileCamera::FileCamera( - int p_id, + const Monitor *monitor, const char *p_path, int p_width, int p_height, @@ -43,7 +35,7 @@ FileCamera::FileCamera( bool p_capture, bool p_record_audio) : Camera( - p_id, + monitor, FILE_SRC, p_width, p_height, @@ -54,22 +46,22 @@ FileCamera::FileCamera( p_hue, p_colour, p_capture, - p_record_audio) + p_record_audio), + path(p_path) { - strncpy( path, p_path, sizeof(path)-1 ); - if ( capture ) { + 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"); } } @@ -78,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; @@ -88,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(0) - 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(Image &image) { - return 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 84201b872..cc5d38acd 100644 --- a/src/zm_file_camera.h +++ b/src/zm_file_camera.h @@ -21,33 +21,37 @@ #define ZM_FILE_CAMERA_H #include "zm_camera.h" -#include "zm_buffer.h" -#include "zm_regexp.h" -#include "zm_packetqueue.h" - -#include // // Class representing 'file' cameras, i.e. those which are // accessed using a single file which contains the latest jpeg data // class FileCamera : public Camera { -protected: - char path[PATH_MAX]; - -public: - FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); - ~FileCamera(); - - const 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( Image &image ); - int PostCapture(); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; -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 new file mode 100644 index 000000000..7ecaaa6b1 --- /dev/null +++ b/src/zm_font.cpp @@ -0,0 +1,122 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "zm_font.h" + +#include +#include + +constexpr uint8 kRequiredZmFntVersion = 1; + +constexpr uint8 FontVariant::kMaxNumCodePoints; +constexpr uint8 FontVariant::kMaxCharHeight; +constexpr uint8 FontVariant::kMaxCharWidth; + +FontVariant::FontVariant() : char_height_(0), char_width_(0), char_padding_(0), codepoint_count_(0) {} + +FontVariant::FontVariant(uint16 char_height, uint16 char_width, uint8 char_padding, std::vector bitmap) + : char_height_(char_height), char_width_(char_width), char_padding_(char_padding), bitmap_(std::move(bitmap)) { + if (char_height_ > kMaxCharHeight) { + throw std::invalid_argument("char_height > kMaxCharHeight"); + } + + if (char_width_ > kMaxCharWidth) { + throw std::invalid_argument("char_width > kMaxCharWidth"); + } + + if (bitmap_.size() % char_height_ != 0) { + throw std::invalid_argument("bitmap has wrong length"); + } + + codepoint_count_ = bitmap_.size() / char_height; +} + +nonstd::span FontVariant::GetCodepoint(uint8 idx) const { + static constexpr std::array empty_bitmap = {}; + + if (idx >= GetCodepointsCount()) { + return {empty_bitmap.begin(), GetCharHeight()}; + } + + return {bitmap_.begin() + (idx * GetCharHeight()), GetCharHeight()}; +} + +std::ifstream &operator>>(std::ifstream &stream, FontBitmapHeader &bm_header) { + stream.read(reinterpret_cast(&bm_header), sizeof(bm_header)); + + return stream; +} + +std::ifstream &operator>>(std::ifstream &stream, FontFileHeader &header) { + stream.read(header.magic, sizeof(header.magic)); + stream.read(reinterpret_cast(&header.version), sizeof(header.version)); + stream.seekg(sizeof(header.pad), std::ifstream::cur); + + for (FontBitmapHeader &bm_header : header.bitmap_header) + stream >> bm_header; + + return stream; +} + +FontLoadError ZmFont::LoadFontFile(const std::string &loc) { + std::ifstream font_file(loc, std::ifstream::binary); + font_file.exceptions(std::ifstream::badbit); + + if (!font_file.is_open()) { + return FontLoadError::kFileNotFound; + } + + FontFileHeader file_header = {}; + font_file >> file_header; + + if (font_file.fail()) { + return FontLoadError::kInvalidFile; + } + + if (memcmp(file_header.magic, "ZMFNT", 5) != 0 || file_header.version != kRequiredZmFntVersion) { + return FontLoadError::kInvalidFile; + } + + for (int i = 0; i < kNumFontSizes; i++) { + FontBitmapHeader bitmap_header = file_header.bitmap_header[i]; + + if (bitmap_header.char_width > FontVariant::kMaxCharWidth + || bitmap_header.char_height > FontVariant::kMaxCharHeight + || bitmap_header.number_of_code_points > FontVariant::kMaxNumCodePoints) { + return FontLoadError::kInvalidFile; + } + + std::vector bitmap; + bitmap.resize(bitmap_header.number_of_code_points * bitmap_header.char_height); + + std::size_t bitmap_bytes = bitmap.size() * sizeof(uint64); + font_file.read(reinterpret_cast(bitmap.data()), static_cast(bitmap_bytes)); + + variants_[i] = + {bitmap_header.char_height, bitmap_header.char_width, bitmap_header.char_padding, std::move(bitmap)}; + } + + if (font_file.fail()) { + return FontLoadError::kInvalidFile; + } + + return FontLoadError::kOk; +} + +const FontVariant &ZmFont::GetFontVariant(uint8 idx) const { + return variants_.at(idx); +} diff --git a/src/zm_font.h b/src/zm_font.h index cdad72dc4..e1f03d7d1 100644 --- a/src/zm_font.h +++ b/src/zm_font.h @@ -1,3337 +1,92 @@ -/**********************************************/ -/* */ -/* Font file generated by rthelen */ -/* */ -/**********************************************/ - -static unsigned char fontdata[] = { - - /* 0 0x00 '^A' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 1 0x01 '^B' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 2 0x02 '^C' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 3 0x03 '^D' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 4 0x04 '^E' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 5 0x05 '^F' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 6 0x06 '^G' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 7 0x07 '^H' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 8 0x08 '^I' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 9 0x09 '^J' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 10 0x0a '^K' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 11 0x0b '^L' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 12 0x0c '^M' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 13 0x0d '^N' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 14 0x0e '^O' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 15 0x0f '^P' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 16 0x10 '^Q' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 17 0x11 '^R' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x54, /* 0 0 0 00 */ - 0x38, /* 00 000 */ - 0x54, /* 0 0 0 00 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 18 0x12 '^S' */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x50, /* 0 0 0000 */ - 0x50, /* 0 0 0000 */ - 0x20, /* 00 00000 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 19 0x13 '^T' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x7c, /* 0 00 */ - 0x38, /* 00 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 20 0x14 '^U' */ - 0x18, /* 000 000 */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x7c, /* 0 00 */ - 0x78, /* 0 000 */ - 0x78, /* 0 000 */ - 0x7c, /* 0 00 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 21 0x15 '^V' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 22 0x16 '^W' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 23 0x17 '^X' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 24 0x18 '^Y' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 25 0x19 '^Z' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 26 0x1a '^[' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 27 0x1b '^\' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 28 0x1c '^]' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 29 0x1d '^^' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 30 0x1e '^_' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 31 0x1f '^`' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 32 0x20 ' ' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 33 0x21 '!' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 34 0x22 '"' */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 35 0x23 '#' */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x7c, /* 0 00 */ - 0x28, /* 00 0 000 */ - 0x7c, /* 0 00 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 36 0x24 '$' */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x54, /* 0 0 0 00 */ - 0x50, /* 0 0 0000 */ - 0x38, /* 00 000 */ - 0x14, /* 000 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x38, /* 00 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 37 0x25 '%' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x54, /* 0 0 0 00 */ - 0x58, /* 0 0 000 */ - 0x28, /* 00 0 000 */ - 0x34, /* 00 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x48, /* 0 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 38 0x26 '&' */ - 0x00, /* 00000000 */ - 0x30, /* 00 0000 */ - 0x48, /* 0 00 000 */ - 0x50, /* 0 0 0000 */ - 0x20, /* 00 00000 */ - 0x54, /* 0 0 0 00 */ - 0x48, /* 0 00 000 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 39 0x27 ''' */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 40 0x28 '(' */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x04, /* 00000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 41 0x29 ')' */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 42 0x2a '*' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x54, /* 0 0 0 00 */ - 0x38, /* 00 000 */ - 0x54, /* 0 0 0 00 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 43 0x2b '+' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x7c, /* 0 00 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 44 0x2c ',' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x30, /* 00 0000 */ - 0x30, /* 00 0000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - - /* 45 0x2d '-' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 46 0x2e '.' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x18, /* 000 000 */ - 0x18, /* 000 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 47 0x2f '/' */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x20, /* 00 00000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x00, /* 00000000 */ - - /* 48 0x30 '0' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x54, /* 0 0 0 00 */ - 0x64, /* 0 00 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 49 0x31 '1' */ - 0x00, /* 00000000 */ - 0x08, /* 0000 000 */ - 0x18, /* 000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x1c, /* 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 50 0x32 '2' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 51 0x33 '3' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x04, /* 00000 00 */ - 0x18, /* 000 000 */ - 0x04, /* 00000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 52 0x34 '4' */ - 0x00, /* 00000000 */ - 0x08, /* 0000 000 */ - 0x18, /* 000 000 */ - 0x28, /* 00 0 000 */ - 0x48, /* 0 00 000 */ - 0x7c, /* 0 00 */ - 0x08, /* 0000 000 */ - 0x1c, /* 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 53 0x35 '5' */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x78, /* 0 000 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 54 0x36 '6' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x40, /* 0 000000 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 55 0x37 '7' */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 56 0x38 '8' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 57 0x39 '9' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x3c, /* 00 00 */ - 0x04, /* 00000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 58 0x3a ':' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x18, /* 000 000 */ - 0x18, /* 000 000 */ - 0x00, /* 00000000 */ - 0x18, /* 000 000 */ - 0x18, /* 000 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 59 0x3b ';' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x30, /* 00 0000 */ - 0x30, /* 00 0000 */ - 0x00, /* 00000000 */ - 0x30, /* 00 0000 */ - 0x30, /* 00 0000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - - /* 60 0x3c '<' */ - 0x00, /* 00000000 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x04, /* 00000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 61 0x3d '=' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 62 0x3e '>' */ - 0x00, /* 00000000 */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 63 0x3f '?' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 64 0x40 '@' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x74, /* 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x78, /* 0 000 */ - 0x40, /* 0 000000 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 65 0x41 'A' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 66 0x42 'B' */ - 0x00, /* 00000000 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x78, /* 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 67 0x43 'C' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 68 0x44 'D' */ - 0x00, /* 00000000 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x78, /* 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 69 0x45 'E' */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x78, /* 0 000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 70 0x46 'F' */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x78, /* 0 000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 71 0x47 'G' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x40, /* 0 000000 */ - 0x4c, /* 0 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 72 0x48 'H' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 73 0x49 'I' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 74 0x4a 'J' */ - 0x00, /* 00000000 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 75 0x4b 'K' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x48, /* 0 00 000 */ - 0x50, /* 0 0 0000 */ - 0x60, /* 0 00000 */ - 0x50, /* 0 0 0000 */ - 0x48, /* 0 00 000 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 76 0x4c 'L' */ - 0x00, /* 00000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 77 0x4d 'M' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x6c, /* 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 78 0x4e 'N' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x64, /* 0 00 00 */ - 0x54, /* 0 0 0 00 */ - 0x4c, /* 0 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 79 0x4f 'O' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 80 0x50 'P' */ - 0x00, /* 00000000 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x78, /* 0 000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 81 0x51 'Q' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x54, /* 0 0 0 00 */ - 0x38, /* 00 000 */ - 0x04, /* 00000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 82 0x52 'R' */ - 0x00, /* 00000000 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 83 0x53 'S' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x40, /* 0 000000 */ - 0x38, /* 00 000 */ - 0x04, /* 00000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 84 0x54 'T' */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 85 0x55 'U' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 86 0x56 'V' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x28, /* 00 0 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 87 0x57 'W' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x6c, /* 0 0 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 88 0x58 'X' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x28, /* 00 0 000 */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 89 0x59 'Y' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x28, /* 00 0 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 90 0x5a 'Z' */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x40, /* 0 000000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 91 0x5b '[' */ - 0x0c, /* 0000 00 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x0c, /* 0000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 92 0x5c '\' */ - 0x20, /* 00 00000 */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x02, /* 000000 0 */ - 0x02, /* 000000 0 */ - 0x00, /* 00000000 */ - - /* 93 0x5d ']' */ - 0x30, /* 00 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x30, /* 00 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 94 0x5e '^' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 95 0x5f '_' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x7e, /* 0 0 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 96 0x60 '`' */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 97 0x61 'a' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 98 0x62 'b' */ - 0x00, /* 00000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x78, /* 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 99 0x63 'c' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x40, /* 0 000000 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 100 0x64 'd' */ - 0x00, /* 00000000 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 101 0x65 'e' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 102 0x66 'f' */ - 0x00, /* 00000000 */ - 0x0c, /* 0000 00 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 103 0x67 'g' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x3c, /* 00 00 */ - 0x04, /* 00000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - - /* 104 0x68 'h' */ - 0x00, /* 00000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 105 0x69 'i' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x30, /* 00 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 106 0x6a 'j' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x30, /* 00 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x60, /* 0 00000 */ - 0x00, /* 00000000 */ - - /* 107 0x6b 'k' */ - 0x00, /* 00000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x48, /* 0 00 000 */ - 0x50, /* 0 0 0000 */ - 0x70, /* 0 0000 */ - 0x48, /* 0 00 000 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 108 0x6c 'l' */ - 0x00, /* 00000000 */ - 0x30, /* 00 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 109 0x6d 'm' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x78, /* 0 000 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 110 0x6e 'n' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x58, /* 0 0 000 */ - 0x64, /* 0 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 111 0x6f 'o' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 112 0x70 'p' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x78, /* 0 000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x00, /* 00000000 */ - - /* 113 0x71 'q' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x3c, /* 00 00 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x00, /* 00000000 */ - - /* 114 0x72 'r' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x58, /* 0 0 000 */ - 0x64, /* 0 00 00 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 115 0x73 's' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x40, /* 0 000000 */ - 0x38, /* 00 000 */ - 0x04, /* 00000 00 */ - 0x78, /* 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 116 0x74 't' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x0c, /* 0000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 117 0x75 'u' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 118 0x76 'v' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x28, /* 00 0 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 119 0x77 'w' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 120 0x78 'x' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x28, /* 00 0 000 */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 121 0x79 'y' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x3c, /* 00 00 */ - 0x04, /* 00000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - - /* 122 0x7a 'z' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 123 0x7b '{' */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x04, /* 00000 00 */ - 0x00, /* 00000000 */ - - /* 124 0x7c '|' */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 125 0x7d '}' */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - - /* 126 0x7e '~' */ - 0x00, /* 00000000 */ - 0x34, /* 00 0 00 */ - 0x58, /* 0 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 127 0x7f '^?' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 128 0x80 '\200' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 129 0x81 '\201' */ - 0x28, /* 00 0 000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 130 0x82 '\202' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 131 0x83 '\203' */ - 0x10, /* 000 0000 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x78, /* 0 000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 132 0x84 '\204' */ - 0x58, /* 0 0 000 */ - 0x44, /* 0 000 00 */ - 0x64, /* 0 00 00 */ - 0x54, /* 0 0 0 00 */ - 0x4c, /* 0 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 133 0x85 '\205' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 134 0x86 '\206' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 135 0x87 '\207' */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 136 0x88 '\210' */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 137 0x89 '\211' */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 138 0x8a '\212' */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 139 0x8b '\213' */ - 0x34, /* 00 0 00 */ - 0x58, /* 0 0 000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 140 0x8c '\214' */ - 0x18, /* 000 000 */ - 0x24, /* 00 00 00 */ - 0x18, /* 000 000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 141 0x8d '\215' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x3c, /* 00 00 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - - /* 142 0x8e '\216' */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 143 0x8f '\217' */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 144 0x90 '\220' */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 145 0x91 '\221' */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 146 0x92 '\222' */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 147 0x93 '\223' */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 148 0x94 '\224' */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 149 0x95 '\225' */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 150 0x96 '\226' */ - 0x34, /* 00 0 00 */ - 0x58, /* 0 0 000 */ - 0x00, /* 00000000 */ - 0x58, /* 0 0 000 */ - 0x64, /* 0 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 151 0x97 '\227' */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 152 0x98 '\230' */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 153 0x99 '\231' */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 154 0x9a '\232' */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 155 0x9b '\233' */ - 0x34, /* 00 0 00 */ - 0x58, /* 0 0 000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 156 0x9c '\234' */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 157 0x9d '\235' */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 158 0x9e '\236' */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 159 0x9f '\237' */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 160 0xa0 '\240' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 161 0xa1 '\241' */ - 0x18, /* 000 000 */ - 0x24, /* 00 00 00 */ - 0x24, /* 00 00 00 */ - 0x18, /* 000 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 162 0xa2 '\242' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x54, /* 0 0 0 00 */ - 0x50, /* 0 0 0000 */ - 0x54, /* 0 0 0 00 */ - 0x38, /* 00 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 163 0xa3 '\243' */ - 0x30, /* 00 0000 */ - 0x48, /* 0 00 000 */ - 0x40, /* 0 000000 */ - 0x70, /* 0 0000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x44, /* 0 000 00 */ - 0x78, /* 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 164 0xa4 '\244' */ - 0x44, /* 0 000 00 */ - 0x24, /* 00 00 00 */ - 0x50, /* 0 0 0000 */ - 0x48, /* 0 00 000 */ - 0x24, /* 00 00 00 */ - 0x14, /* 000 0 00 */ - 0x48, /* 0 00 000 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 165 0xa5 '\245' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x7c, /* 0 00 */ - 0x7c, /* 0 00 */ - 0x7c, /* 0 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 166 0xa6 '\246' */ - 0x3c, /* 00 00 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x3c, /* 00 00 */ - 0x14, /* 000 0 00 */ - 0x14, /* 000 0 00 */ - 0x14, /* 000 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 167 0xa7 '\247' */ - 0x18, /* 000 000 */ - 0x24, /* 00 00 00 */ - 0x44, /* 0 000 00 */ - 0x48, /* 0 00 000 */ - 0x48, /* 0 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x58, /* 0 0 000 */ - 0x40, /* 0 000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 168 0xa8 '\250' */ - 0x00, /* 00000000 */ - 0x70, /* 0 0000 */ - 0x08, /* 0000 000 */ - 0x64, /* 0 00 00 */ - 0x54, /* 0 0 0 00 */ - 0x64, /* 0 00 00 */ - 0x58, /* 0 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 169 0xa9 '\251' */ - 0x00, /* 00000000 */ - 0x70, /* 0 0000 */ - 0x08, /* 0000 000 */ - 0x34, /* 00 0 00 */ - 0x44, /* 0 000 00 */ - 0x34, /* 00 0 00 */ - 0x08, /* 0000 000 */ - 0x70, /* 0 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 170 0xaa '\252' */ - 0x00, /* 00000000 */ - 0x7a, /* 0 0 0 */ - 0x2e, /* 00 0 0 */ - 0x2e, /* 00 0 0 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 171 0xab '\253' */ - 0x00, /* 00000000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 172 0xac '\254' */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 173 0xad '\255' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x08, /* 0000 000 */ - 0x7c, /* 0 00 */ - 0x10, /* 000 0000 */ - 0x7c, /* 0 00 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 174 0xae '\256' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x50, /* 0 0 0000 */ - 0x50, /* 0 0 0000 */ - 0x78, /* 0 000 */ - 0x50, /* 0 0 0000 */ - 0x50, /* 0 0 0000 */ - 0x5c, /* 0 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 175 0xaf '\257' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x54, /* 0 0 0 00 */ - 0x64, /* 0 00 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 176 0xb0 '\260' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x6c, /* 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x6c, /* 0 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 177 0xb1 '\261' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x7c, /* 0 00 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 178 0xb2 '\262' */ - 0x00, /* 00000000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 179 0xb3 '\263' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x1c, /* 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 180 0xb4 '\264' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x28, /* 00 0 000 */ - 0x7c, /* 0 00 */ - 0x10, /* 000 0000 */ - 0x7c, /* 0 00 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 181 0xb5 '\265' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x48, /* 0 00 000 */ - 0x48, /* 0 00 000 */ - 0x48, /* 0 00 000 */ - 0x48, /* 0 00 000 */ - 0x74, /* 0 0 00 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x00, /* 00000000 */ - - /* 182 0xb6 '\266' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x0c, /* 0000 00 */ - 0x14, /* 000 0 00 */ - 0x24, /* 00 00 00 */ - 0x24, /* 00 00 00 */ - 0x18, /* 000 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 183 0xb7 '\267' */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x24, /* 00 00 00 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x24, /* 00 00 00 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 184 0xb8 '\270' */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 185 0xb9 '\271' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 186 0xba '\272' */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x60, /* 0 00000 */ - 0x00, /* 00000000 */ - - /* 187 0xbb '\273' */ - 0x00, /* 00000000 */ - 0x1c, /* 000 00 */ - 0x24, /* 00 00 00 */ - 0x24, /* 00 00 00 */ - 0x1c, /* 000 00 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 188 0xbc '\274' */ - 0x00, /* 00000000 */ - 0x18, /* 000 000 */ - 0x24, /* 00 00 00 */ - 0x24, /* 00 00 00 */ - 0x18, /* 000 000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 189 0xbd '\275' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x28, /* 00 0 000 */ - 0x6c, /* 0 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 190 0xbe '\276' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x54, /* 0 0 0 00 */ - 0x5c, /* 0 0 00 */ - 0x50, /* 0 0 0000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 191 0xbf '\277' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x4c, /* 0 00 00 */ - 0x54, /* 0 0 0 00 */ - 0x64, /* 0 00 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 192 0xc0 '\300' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x40, /* 0 000000 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 193 0xc1 '\301' */ - 0x00, /* 00000000 */ - 0x08, /* 0000 000 */ - 0x00, /* 00000000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 194 0xc2 '\302' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 195 0xc3 '\303' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x0c, /* 0000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x50, /* 0 0 0000 */ - 0x20, /* 00 00000 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 196 0xc4 '\304' */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x60, /* 0 00000 */ - 0x00, /* 00000000 */ - - /* 197 0xc5 '\305' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x04, /* 00000 00 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x40, /* 0 000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 198 0xc6 '\306' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 199 0xc7 '\307' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x24, /* 00 00 00 */ - 0x48, /* 0 00 000 */ - 0x48, /* 0 00 000 */ - 0x24, /* 00 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 200 0xc8 '\310' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x48, /* 0 00 000 */ - 0x24, /* 00 00 00 */ - 0x24, /* 00 00 00 */ - 0x48, /* 0 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 201 0xc9 '\311' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x54, /* 0 0 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 202 0xca '\312' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 203 0xcb '\313' */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 204 0xcc '\314' */ - 0x58, /* 0 0 000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 205 0xcd '\315' */ - 0x58, /* 0 0 000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 206 0xce '\316' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x50, /* 0 0 0000 */ - 0x50, /* 0 0 0000 */ - 0x58, /* 0 0 000 */ - 0x50, /* 0 0 0000 */ - 0x50, /* 0 0 0000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 207 0xcf '\317' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x54, /* 0 0 0 00 */ - 0x5c, /* 0 0 00 */ - 0x50, /* 0 0 0000 */ - 0x2c, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 208 0xd0 '\320' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 209 0xd1 '\321' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x7e, /* 0 0 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 210 0xd2 '\322' */ - 0x00, /* 00000000 */ - 0x14, /* 000 0 00 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 211 0xd3 '\323' */ - 0x00, /* 00000000 */ - 0x14, /* 000 0 00 */ - 0x14, /* 000 0 00 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 212 0xd4 '\324' */ - 0x00, /* 00000000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x18, /* 000 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 213 0xd5 '\325' */ - 0x00, /* 00000000 */ - 0x18, /* 000 000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 214 0xd6 '\326' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 215 0xd7 '\327' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x44, /* 0 000 00 */ - 0x28, /* 00 0 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 216 0xd8 '\330' */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x3c, /* 00 00 */ - 0x04, /* 00000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - - /* 217 0xd9 '\331' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x7e, /* 0 0 */ - 0x00, /* 00000000 */ - 0x7e, /* 0 0 */ - 0x00, /* 00000000 */ - 0x7e, /* 0 0 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 218 0xda '\332' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 219 0xdb '\333' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 220 0xdc '\334' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 221 0xdd '\335' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 222 0xde '\336' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 223 0xdf '\337' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 224 0xe0 '\340' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 225 0xe1 '\341' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 226 0xe2 '\342' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 227 0xe3 '\343' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 228 0xe4 '\344' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 229 0xe5 '\345' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 230 0xe6 '\346' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 231 0xe7 '\347' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 232 0xe8 '\350' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 233 0xe9 '\351' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 234 0xea '\352' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 235 0xeb '\353' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 236 0xec '\354' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 237 0xed '\355' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 238 0xee '\356' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 239 0xef '\357' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 240 0xf0 '\360' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 241 0xf1 '\361' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 242 0xf2 '\362' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 243 0xf3 '\363' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 244 0xf4 '\364' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 245 0xf5 '\365' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 246 0xf6 '\366' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 247 0xf7 '\367' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 248 0xf8 '\370' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 249 0xf9 '\371' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 250 0xfa '\372' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 251 0xfb '\373' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 252 0xfc '\374' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 253 0xfd '\375' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 254 0xfe '\376' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 255 0xff '\377' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef ZM_FONT_H +#define ZM_FONT_H + +#include "zm_define.h" +#include +#include "span.hpp" +#include +#include + +constexpr uint8 kNumFontSizes = 4; + +enum class FontLoadError { + kOk, + kFileNotFound, + kInvalidFile }; + +#pragma pack(push, 1) +struct FontBitmapHeader { + uint16 char_height; // height of every character + uint16 char_width; // width of every character + uint32 number_of_code_points; // number of codepoints; max. 255 for now + uint32 idx; // offset in data where data for the bitmap starts; not used + uint8 char_padding; // padding around characters + uint8 pad[3]; // struct padding +}; +#pragma pack(pop) + +#pragma pack(push, 1) +struct FontFileHeader { + char magic[6]; // "ZMFNT\0" + uint8 version; + uint8 pad; + std::array bitmap_header; +}; +#pragma pack(pop) + +class FontVariant { + public: + static constexpr uint8 kMaxNumCodePoints = 255; + // height cannot be greater than 200 (arbitrary number; shouldn't need more than this) + static constexpr uint8 kMaxCharHeight = 200; + // character width can't be greater than 64 as a row is represented as an uint64 + static constexpr uint8 kMaxCharWidth = 64; + + FontVariant(); + FontVariant(uint16 char_height, uint16 char_width, uint8 char_padding, std::vector bitmap); + + uint16 GetCharHeight() const { return char_height_; } + uint16 GetCharWidth() const { return char_width_; } + uint8 GetCharPadding() const { return char_padding_; } + uint8 GetCodepointsCount() const { return codepoint_count_; } + + // Returns the bitmap of the codepoint `idx`. If `idx` is greater than `GetCodepointsCount` + // a all-zero bitmap with `GetCharHeight` elements is returned. + nonstd::span GetCodepoint(uint8 idx) const; + + private: + uint16 char_height_; + uint16 char_width_; + uint8 char_padding_; + uint8 codepoint_count_; + std::vector bitmap_; +}; + +class ZmFont { + public: + FontLoadError LoadFontFile(const std::string &loc); + const FontVariant &GetFontVariant(uint8 idx) const; + + private: + std::array variants_; +}; + +#endif diff --git a/src/zm_frame.cpp b/src/zm_frame.cpp index bf47bdbf5..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 84190e779..77c7a0398 100644 --- a/src/zm_frame.h +++ b/src/zm_frame.h @@ -20,35 +20,38 @@ #ifndef ZM_FRAME_H #define ZM_FRAME_H -#include -#include -class Frame; - #include "zm_event.h" #include "zm_time.h" +#include "zm_zone.h" +#include + +enum FrameType { + NORMAL = 0, + BULK, + ALARM +}; // // This describes a frame record // class Frame { + public: + Frame(event_id_t p_event_id, + int p_frame_id, + FrameType p_type, + 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 070d3b571..9ed7a1093 100644 --- a/src/zm_group.cpp +++ b/src/zm_group.cpp @@ -15,15 +15,13 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "zm.h" -#include "zm_db.h" +*/ #include "zm_group.h" -#include -#include +#include "zm_logger.h" +#include "zm_utils.h" +#include Group::Group() { Warning("Instantiating default Group Object. Should not happen."); @@ -33,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++; @@ -42,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 6adb8cd93..f3ce1f3cf 100644 --- a/src/zm_group.h +++ b/src/zm_group.h @@ -17,11 +17,11 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include "zm_db.h" - #ifndef ZM_GROUP_H #define ZM_GROUP_H +#include "zm_db.h" + class Group { protected: @@ -31,7 +31,7 @@ protected: public: Group(); - explicit Group( MYSQL_ROW &dbrow ); + explicit Group(const MYSQL_ROW &dbrow ); explicit Group( unsigned int p_id ); ~Group(); diff --git a/src/zm_image.cpp b/src/zm_image.cpp index 97c2132c6..ea785195e 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -16,17 +16,17 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" -#include "zm_font.h" -#include "zm_bigfont.h" -#include "zm_image.h" -#include "zm_utils.h" -#include "zm_rgb.h" -#include "zm_ffmpeg.h" +#include "zm_image.h" + +#include "zm_font.h" +#include "zm_poly.h" +#include "zm_utils.h" +#include #include +#include #include -#include +#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}; @@ -48,10 +48,12 @@ static short *g_v_table; static short *g_u_table; static short *b_u_table; -jpeg_compress_struct *Image::writejpg_ccinfo[101] = { 0 }; -jpeg_compress_struct *Image::encodejpg_ccinfo[101] = { 0 }; -jpeg_decompress_struct *Image::readjpg_dcinfo = 0; -jpeg_decompress_struct *Image::decodejpg_dcinfo = 0; +struct SwsContext *sws_convert_context = nullptr; + +jpeg_compress_struct *Image::writejpg_ccinfo[101] = { }; +jpeg_compress_struct *Image::encodejpg_ccinfo[101] = { }; +jpeg_decompress_struct *Image::readjpg_dcinfo = nullptr; +jpeg_decompress_struct *Image::decodejpg_dcinfo = nullptr; struct zm_error_mgr Image::jpg_err; /* Pointer to blend function. */ @@ -76,6 +78,11 @@ static deinterlace_4field_fptr_t fptr_deinterlace_4field_gray8; /* Pointer to image buffer memory copy function */ imgbufcpy_fptr_t fptr_imgbufcpy; +/* Font */ +static ZmFont font; + +std::mutex jpeg_mutex; + void Image::update_function_pointers() { /* Because many loops are unrolled and work on 16 colours/time or 4 pixels/time, we have to meet requirements */ if ( pixels % 16 || pixels % 12 ) { @@ -88,6 +95,7 @@ void Image::update_function_pointers() { delta8_abgr = &std_delta8_abgr; delta8_gray8 = &std_delta8_gray8; blend = &std_blend; + Warning("Using slow std functions"); } else { // Use either sse or neon, or loop unrolled version delta8_rgb = fptr_delta8_rgb; @@ -102,51 +110,98 @@ void Image::update_function_pointers() { } // This constructor is not used anywhere -Image::Image() { - if ( !initialised ) +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), + width(0), + linesize(0), + height(0), + pixels(0), + colours(0), + padding(0), + size(0), + subpixelorder(0), + allocation(0), + buffer(nullptr), + buffertype(ZM_BUFTYPE_DONTFREE), + holdbuffer(0) +{ + if (!initialised) Initialise(); - width = 0; - height = 0; - pixels = 0; - colours = 0; - subpixelorder = 0; - size = 0; - allocation = 0; - buffer = 0; - buffertype = 0; - holdbuffer = 0; - text[0] = '\0'; + // Update blend to fast function determined by Initialise, I'm sure this can be improve. blend = fptr_blend; } -Image::Image( const char *filename ) { +Image::Image(const char *filename) { if ( !initialised ) Initialise(); width = 0; + linesize = 0; height = 0; + padding = 0; pixels = 0; colours = 0; subpixelorder = 0; size = 0; allocation = 0; buffer = 0; - buffertype = 0; + buffertype = ZM_BUFTYPE_DONTFREE; holdbuffer = 0; ReadJpeg(filename, ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB); - text[0] = '\0'; update_function_pointers(); } -Image::Image( int p_width, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer ) { +Image::Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer, unsigned int p_padding) : + width(p_width), + height(p_height), + colours(p_colours), + padding(p_padding), + subpixelorder(p_subpixelorder), + buffer(p_buffer), + holdbuffer(0) +{ + + if (!initialised) + Initialise(); + pixels = width * height; + linesize = p_width * p_colours; + size = linesize * height + padding; + if (p_buffer) { + allocation = size; + buffertype = ZM_BUFTYPE_DONTFREE; + buffer = p_buffer; + } else { + AllocImgBuffer(size); + } + if (!subpixelorder and (colours>1)) { + // Default to RGBA when no subpixelorder is specified. + subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } + imagePixFormat = AVPixFormat(); + + update_function_pointers(); +} + +Image::Image(int p_width, int p_linesize, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer, unsigned int p_padding) : + width(p_width), + linesize(p_linesize), + height(p_height), + colours(p_colours), + padding(p_padding), + subpixelorder(p_subpixelorder), + buffer(p_buffer) +{ if ( !initialised ) Initialise(); - width = p_width; - height = p_height; pixels = width*height; - colours = p_colours; - subpixelorder = p_subpixelorder; - size = pixels*colours; - buffer = 0; + size = linesize*height + padding; + buffer = nullptr; holdbuffer = 0; if ( p_buffer ) { allocation = size; @@ -155,67 +210,135 @@ Image::Image( int p_width, int p_height, int p_colours, int p_subpixelorder, uin } else { AllocImgBuffer(size); } - text[0] = '\0'; + if (!subpixelorder and colours>1) { + // Default to RGBA when no subpixelorder is specified. + subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } + imagePixFormat = AVPixFormat(); update_function_pointers(); } -Image::Image( const AVFrame *frame ) { - AVFrame *dest_frame = zm_av_frame_alloc(); - text[0] = '\0'; - +Image::Image(const AVFrame *frame) : + colours(ZM_COLOUR_RGB32), + padding(0), + subpixelorder(ZM_SUBPIX_ORDER_RGBA), + imagePixFormat(AV_PIX_FMT_RGBA), + buffer(0), + holdbuffer(0) +{ width = frame->width; height = frame->height; pixels = width*height; - colours = ZM_COLOUR_RGB32; - subpixelorder = ZM_SUBPIX_ORDER_RGBA; - size = pixels*colours; - buffer = 0; - holdbuffer = 0; + + zm_dump_video_frame(frame, "Image.Assign(frame)"); + // FIXME + //(AVPixelFormat)frame->format; + + 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); + AllocImgBuffer(size); - -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - av_image_fill_arrays(dest_frame->data, dest_frame->linesize, - buffer, AV_PIX_FMT_RGBA, width, height, 1); -#else - avpicture_fill( (AVPicture *)dest_frame, buffer, - AV_PIX_FMT_RGBA, width, height); -#endif - -#if HAVE_LIBSWSCALE - struct SwsContext *mConvertContext = sws_getContext( - width, - height, - (AVPixelFormat)frame->format, - width, height, - AV_PIX_FMT_RGBA, SWS_BICUBIC, NULL, - NULL, NULL); - if ( mConvertContext == NULL ) - Fatal( "Unable to create conversion context" ); - - if ( sws_scale(mConvertContext, frame->data, frame->linesize, 0, frame->height, dest_frame->data, dest_frame->linesize) < 0 ) - Fatal("Unable to convert raw format %u to target format %u", frame->format, AV_PIX_FMT_RGBA); -#else // HAVE_LIBSWSCALE - Fatal("You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras"); -#endif // HAVE_LIBSWSCALE - av_frame_free( &dest_frame ); - update_function_pointers(); + this->Assign(frame); } -Image::Image( const Image &p_image ) { +static void dont_free(void *opaque, uint8_t *data) { +} + +int Image::PopulateFrame(AVFrame *frame) { + Debug(1, "PopulateFrame: width %d height %d linesize %d colours %d imagesize %d %s", + width, height, linesize, colours, size, + av_get_pix_fmt_name(imagePixFormat) + ); + AVBufferRef *ref = av_buffer_create(buffer, size, + dont_free, /* Free callback */ + nullptr, /* opaque */ + 0 /* flags */ + ); + if (!ref) { + Warning("Failed to create av_buffer"); + } + frame->buf[0] = ref; + + // From what I've read, we should align the linesizes to 32bit so that ffmpeg can use SIMD instructions too. + int size = av_image_fill_arrays( + frame->data, frame->linesize, + buffer, imagePixFormat, width, height, + 32 //alignment + ); + if ( size < 0 ) { + Error("Problem setting up data pointers into image %s", + av_make_error_string(size).c_str()); + return size; + } + + frame->width = width; + frame->height = height; + frame->format = imagePixFormat; + zm_dump_video_frame(frame, "Image.Populate(frame)"); + return 1; +} // int Image::PopulateFrame(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(); + sws_convert_context = sws_getCachedContext( + sws_convert_context, + frame->width, frame->height, (AVPixelFormat)frame->format, + width, height, format, + SWS_BICUBIC, + //SWS_POINT | SWS_BITEXACT, + nullptr, nullptr, nullptr); + if (sws_convert_context == nullptr) { + Error("Unable to create conversion context"); + return false; + } + bool result = Assign(frame, sws_convert_context, dest_frame); + av_frame_free(&dest_frame); + update_function_pointers(); + 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; subpixelorder = p_image.subpixelorder; size = p_image.size; // allocation is set in AllocImgBuffer - buffer = 0; + buffer = nullptr; 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(); } @@ -225,59 +348,60 @@ Image::~Image() { /* Should be called as part of program shutdown to free everything */ void Image::Deinitialise() { - if ( initialised ) { - /* - delete[] y_table; - delete[] uv_table; - delete[] r_v_table; - delete[] g_v_table; - delete[] g_u_table; - delete[] b_u_table; - */ - initialised = false; - if ( readjpg_dcinfo ) { - jpeg_destroy_decompress( readjpg_dcinfo ); - delete readjpg_dcinfo; - readjpg_dcinfo = 0; - } - if ( decodejpg_dcinfo ) { - jpeg_destroy_decompress( decodejpg_dcinfo ); - delete decodejpg_dcinfo; - decodejpg_dcinfo = 0; - } - 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] = NULL; - } - } // end foreach quality + if (!initialised) return; + initialised = false; + if (readjpg_dcinfo) { + jpeg_destroy_decompress(readjpg_dcinfo); + delete readjpg_dcinfo; + readjpg_dcinfo = nullptr; } -} + 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]) { + 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) { + sws_freeContext(sws_convert_context); + sws_convert_context = nullptr; + } +} // end void Image::Deinitialise() void Image::Initialise() { /* Assign the blend pointer to function */ if ( config.fast_image_blends ) { - if ( config.cpu_extensions && sseversion >= 20 ) { + if ( config.cpu_extensions && sse_version >= 20 ) { fptr_blend = &sse2_fastblend; /* SSE2 fast blend */ - Debug(4,"Blend: Using SSE2 fast blend function"); + Debug(4, "Blend: Using SSE2 fast blend function"); } else if ( config.cpu_extensions && neonversion >= 1 ) { #if defined(__aarch64__) fptr_blend = &neon64_armv8_fastblend; /* ARM Neon (AArch64) fast blend */ - Debug(4,"Blend: Using ARM Neon (AArch64) fast blend function"); + Debug(4, "Blend: Using ARM Neon (AArch64) fast blend function"); #elif defined(__arm__) fptr_blend = &neon32_armv7_fastblend; /* ARM Neon (AArch32) fast blend */ - Debug(4,"Blend: Using ARM Neon (AArch32) fast blend function"); + Debug(4, "Blend: Using ARM Neon (AArch32) fast blend function"); #else Panic("Bug: Non ARM platform but neon present"); #endif } else { fptr_blend = &std_fastblend; /* standard fast blend */ - Debug(4,"Blend: Using fast blend function"); + Debug(4, "Blend: Using fast blend function"); } } else { fptr_blend = &std_blend; - Debug(4,"Blend: Using standard blend function"); + Debug(4, "Blend: Using standard blend function"); } __attribute__((aligned(64))) uint8_t blend1[128] = { @@ -304,7 +428,7 @@ void Image::Initialise() { (*fptr_blend)(blend1,blend2,blendres,128,12.0); /* Compare results with expected results */ - for ( int i=0; i < 128; i ++ ) { + for ( int i=0; i < 128; i++ ) { if ( abs(blendexp[i] - blendres[i]) > 3 ) { Panic("Blend function failed self-test: Results differ from the expected results. Column %u Expected %u Got %u",i,blendexp[i],blendres[i]); } @@ -315,22 +439,22 @@ void Image::Initialise() { /* Assign the delta functions */ if ( config.cpu_extensions ) { - if ( sseversion >= 35 ) { + if ( sse_version >= 35 ) { /* SSSE3 available */ fptr_delta8_rgba = &ssse3_delta8_rgba; fptr_delta8_bgra = &ssse3_delta8_bgra; fptr_delta8_argb = &ssse3_delta8_argb; fptr_delta8_abgr = &ssse3_delta8_abgr; fptr_delta8_gray8 = &sse2_delta8_gray8; - Debug(4,"Delta: Using SSSE3 delta functions"); - } else if ( sseversion >= 20 ) { + Debug(4, "Delta: Using SSSE3 delta functions"); + } else if ( sse_version >= 20 ) { /* SSE2 available */ fptr_delta8_rgba = &sse2_delta8_rgba; fptr_delta8_bgra = &sse2_delta8_bgra; fptr_delta8_argb = &sse2_delta8_argb; fptr_delta8_abgr = &sse2_delta8_abgr; fptr_delta8_gray8 = &sse2_delta8_gray8; - Debug(4,"Delta: Using SSE2 delta functions"); + Debug(4, "Delta: Using SSE2 delta functions"); } else if ( neonversion >= 1 ) { /* ARM Neon available */ #if defined(__aarch64__) @@ -339,14 +463,14 @@ void Image::Initialise() { fptr_delta8_argb = &neon64_armv8_delta8_argb; fptr_delta8_abgr = &neon64_armv8_delta8_abgr; fptr_delta8_gray8 = &neon64_armv8_delta8_gray8; - Debug(4,"Delta: Using ARM Neon (AArch64) delta functions"); + Debug(4, "Delta: Using ARM Neon (AArch64) delta functions"); #elif defined(__arm__) fptr_delta8_rgba = &neon32_armv7_delta8_rgba; fptr_delta8_bgra = &neon32_armv7_delta8_bgra; fptr_delta8_argb = &neon32_armv7_delta8_argb; fptr_delta8_abgr = &neon32_armv7_delta8_abgr; fptr_delta8_gray8 = &neon32_armv7_delta8_gray8; - Debug(4,"Delta: Using ARM Neon (AArch32) delta functions"); + Debug(4, "Delta: Using ARM Neon (AArch32) delta functions"); #else Panic("Bug: Non ARM platform but neon present"); #endif @@ -404,7 +528,7 @@ void Image::Initialise() { } /* Run the delta8 RGBA function */ - (*fptr_delta8_rgba)(delta8_1,delta8_2,delta8_rgba_res,32); + (*fptr_delta8_rgba)(delta8_1,delta8_2,delta8_rgba_res, 32); /* Compare results with expected results */ for ( int i=0; i < 32; i++ ) { @@ -423,95 +547,70 @@ void Image::Initialise() { fptr_deinterlace_4field_argb = &std_deinterlace_4field_argb; fptr_deinterlace_4field_abgr = &std_deinterlace_4field_abgr; fptr_deinterlace_4field_gray8 = &std_deinterlace_4field_gray8; - Debug(4,"Deinterlace: Using standard functions"); + Debug(4, "Deinterlace: Using standard functions"); #if defined(__i386__) && !defined(__x86_64__) /* Use SSE2 aligned memory copy? */ - if ( config.cpu_extensions && sseversion >= 20 ) { + if ( config.cpu_extensions && sse_version >= 20 ) { fptr_imgbufcpy = &sse2_aligned_memcpy; - Debug(4,"Image buffer copy: Using SSE2 aligned memcpy"); + Debug(4, "Image buffer copy: Using SSE2 aligned memcpy"); } else { fptr_imgbufcpy = &memcpy; - Debug(4,"Image buffer copy: Using standard memcpy"); + Debug(4, "Image buffer copy: Using standard memcpy"); } #else fptr_imgbufcpy = &memcpy; - Debug(4,"Image buffer copy: Using standard memcpy"); + Debug(4, "Image buffer copy: Using standard memcpy"); #endif - /* Code below relocated from zm_local_camera */ - Debug( 3, "Setting up static colour tables" ); - y_table = y_table_global; uv_table = uv_table_global; r_v_table = r_v_table_global; g_v_table = g_v_table_global; g_u_table = g_u_table_global; b_u_table = b_u_table_global; - /* - y_table = new unsigned char[256]; - for ( int i = 0; i <= 255; i++ ) - { - unsigned char c = i; - if ( c <= 16 ) - y_table[c] = 0; - else if ( c >= 235 ) - y_table[c] = 255; - else - y_table[c] = (255*(c-16))/219; - } - - uv_table = new signed char[256]; - for ( int i = 0; i <= 255; i++ ) - { - unsigned char c = i; - if ( c <= 16 ) - uv_table[c] = -127; - else if ( c >= 240 ) - uv_table[c] = 127; - else - uv_table[c] = (127*(c-128))/112; - } - - r_v_table = new short[255]; - g_v_table = new short[255]; - g_u_table = new short[255]; - b_u_table = new short[255]; - for ( int i = 0; i < 255; i++ ) - { - r_v_table[i] = (1402*(i-128))/1000; - g_u_table[i] = (344*(i-128))/1000; - g_v_table[i] = (714*(i-128))/1000; - b_u_table[i] = (1772*(i-128))/1000; - } - */ + FontLoadError res = font.LoadFontFile(config.font_file_location); + if ( res == FontLoadError::kFileNotFound ) { + Panic("Invalid font location: %s", config.font_file_location); + } else if ( res == FontLoadError::kInvalidFile ) { + Panic("Invalid font file."); + } initialised = true; } /* Requests a writeable buffer to the image. This is safer than buffer() because this way we can guarantee that a buffer of required size exists */ -uint8_t* Image::WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder) { +uint8_t* Image::WriteBuffer( + const unsigned int p_width, + const unsigned int p_height, + const unsigned int p_colours, + const unsigned int p_subpixelorder) { - if ( p_colours != ZM_COLOUR_GRAY8 && p_colours != ZM_COLOUR_RGB24 && p_colours != ZM_COLOUR_RGB32 ) { - Error("WriteBuffer called with unexpected colours: %d",p_colours); - return NULL; + if ( p_colours != ZM_COLOUR_GRAY8 + && + p_colours != ZM_COLOUR_RGB24 + && + p_colours != ZM_COLOUR_RGB32 ) { + Error("WriteBuffer called with unexpected colours: %d", p_colours); + return nullptr; } if ( ! ( p_height > 0 && p_width > 0 ) ) { Error("WriteBuffer called with invalid width or height: %d %d", p_width, p_height); - return NULL; + return nullptr; } if ( p_width != width || p_height != height || p_colours != colours || p_subpixelorder != subpixelorder ) { + unsigned int newsize = (p_width * p_height) * p_colours; - if ( buffer == NULL ) { + if ( buffer == nullptr ) { AllocImgBuffer(newsize); } else { if ( allocation < newsize ) { if ( holdbuffer ) { Error("Held buffer is undersized for requested buffer"); - return NULL; + return nullptr; } else { /* Replace buffer with a bigger one */ //DumpImgBuffer(); // Done in AllocImgBuffer too @@ -523,35 +622,47 @@ uint8_t* Image::WriteBuffer(const unsigned int p_width, const unsigned int p_hei width = p_width; height = p_height; colours = p_colours; + linesize = p_width * p_colours; subpixelorder = p_subpixelorder; pixels = height*width; size = newsize; - } // end if need to re-alloc buffer + } // end if need to re-alloc buffer return buffer; } -/* Assign an existing buffer to the image instead of copying from a source buffer. The goal is to reduce the amount of memory copying and increase efficiency and buffer reusing. */ -void Image::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) { - if ( new_buffer == NULL ) { +/* Assign an existing buffer to the image instead of copying from a source buffer. + The goal is to reduce the amount of memory copying and increase efficiency and buffer reusing. +*/ +void Image::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) { + + if ( new_buffer == nullptr ) { Error("Attempt to directly assign buffer from a NULL pointer"); return; } if ( !p_height || !p_width ) { - Error("Attempt to directly assign buffer with invalid width or height: %d %d",p_width,p_height); + Error("Attempt to directly assign buffer with invalid width or height: %d %d", p_width, p_height); return; } if ( p_colours != ZM_COLOUR_GRAY8 && p_colours != ZM_COLOUR_RGB24 && p_colours != ZM_COLOUR_RGB32 ) { - Error("Attempt to directly assign buffer with unexpected colours per pixel: %d",p_colours); + Error("Attempt to directly assign buffer with unexpected colours per pixel: %d", p_colours); return; } - unsigned int new_buffer_size = ((p_width*p_height)*p_colours); + size_t new_buffer_size = p_width * p_height * p_colours; if ( buffer_size < new_buffer_size ) { - Error("Attempt to directly assign buffer from an undersized buffer of size: %zu, needed %dx%d*%d colours = %zu",buffer_size, p_width, p_height, p_colours, new_buffer_size ); + Error("Attempt to directly assign buffer from an undersized buffer of size: %zu, needed %dx%d*%d colours = %zu", + buffer_size, p_width, p_height, p_colours, new_buffer_size); return; } @@ -560,58 +671,57 @@ void Image::AssignDirect( const unsigned int p_width, const unsigned int p_heigh Error("Held buffer is undersized for assigned buffer"); return; } else { - width = p_width; - height = p_height; - colours = p_colours; - subpixelorder = p_subpixelorder; - pixels = height*width; - size = new_buffer_size; // was pixels*colours, but we already calculated it above as new_buffer_size - /* Copy into the held buffer */ - if ( new_buffer != buffer ) + if ( new_buffer != buffer ) { (*fptr_imgbufcpy)(buffer, new_buffer, size); - + } /* Free the new buffer */ DumpBuffer(new_buffer, p_buffertype); } } else { /* Free an existing buffer if any */ DumpImgBuffer(); - - width = p_width; - height = p_height; - colours = p_colours; - subpixelorder = p_subpixelorder; - pixels = height*width; - size = new_buffer_size; // was pixels*colours, but we already calculated it above as new_buffer_size - allocation = buffer_size; buffertype = p_buffertype; buffer = new_buffer; } -} + width = p_width; + height = p_height; + colours = p_colours; + linesize = width * colours; + subpixelorder = p_subpixelorder; + pixels = width * height; + size = new_buffer_size; + update_function_pointers(); +} // end void Image::AssignDirect -void Image::Assign(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, const uint8_t* new_buffer, const size_t buffer_size) { - unsigned int new_size = (p_width * p_height) * p_colours; +void Image::Assign( + const unsigned int p_width, + const unsigned int p_height, + const unsigned int p_colours, + const unsigned int p_subpixelorder, + const uint8_t* new_buffer, + const size_t buffer_size) { - if ( new_buffer == NULL ) { + if ( new_buffer == nullptr ) { Error("Attempt to assign buffer from a NULL pointer"); return; } + unsigned int new_size = p_width * p_height * p_colours; if ( buffer_size < new_size ) { - Error("Attempt to assign buffer from an undersized buffer of size: %zu",buffer_size); + Error("Attempt to assign buffer from an undersized buffer of size: %zu", buffer_size); return; } if ( !p_height || !p_width ) { - Error("Attempt to assign buffer with invalid width or height: %d %d",p_width,p_height); + Error("Attempt to assign buffer with invalid width or height: %d %d", p_width, p_height); return; } if ( p_colours != ZM_COLOUR_GRAY8 && p_colours != ZM_COLOUR_RGB24 && p_colours != ZM_COLOUR_RGB32 ) { - Error("Attempt to assign buffer with unexpected colours per pixel: %d",p_colours); + Error("Attempt to assign buffer with unexpected colours per pixel: %d", p_colours); return; } @@ -623,7 +733,7 @@ void Image::Assign(const unsigned int p_width, const unsigned int p_height, cons return; } } else { - if ( new_size > allocation || !buffer ) { + if ( (new_size > allocation) || !buffer ) { DumpImgBuffer(); AllocImgBuffer(new_size); } @@ -637,33 +747,40 @@ void Image::Assign(const unsigned int p_width, const unsigned int p_height, cons size = new_size; } - if(new_buffer != buffer) + if ( new_buffer != buffer ) (*fptr_imgbufcpy)(buffer, new_buffer, size); - + update_function_pointers(); } -void Image::Assign( const Image &image ) { - unsigned int new_size = (image.width * image.height) * image.colours; +void Image::Assign(const Image &image) { + unsigned int new_size = image.height * image.linesize; - if ( image.buffer == NULL ) { + if ( image.buffer == nullptr ) { Error("Attempt to assign image with an empty buffer"); return; } - if ( image.colours != ZM_COLOUR_GRAY8 && image.colours != ZM_COLOUR_RGB24 && image.colours != ZM_COLOUR_RGB32 ) { - Error("Attempt to assign image with unexpected colours per pixel: %d",image.colours); + if ( image.colours != ZM_COLOUR_GRAY8 + && + image.colours != ZM_COLOUR_RGB24 + && + image.colours != ZM_COLOUR_RGB32 ) { + Error("Attempt to assign image with unexpected colours per pixel: %d", image.colours); return; } - if ( !buffer || image.width != width || image.height != height || image.colours != colours || image.subpixelorder != subpixelorder) { + if ( !buffer + || image.width != width || image.height != height + || image.colours != colours || image.subpixelorder != subpixelorder + ) { - if (holdbuffer && buffer) { - if (new_size > allocation) { + if ( holdbuffer && buffer ) { + if ( new_size > allocation ) { Error("Held buffer is undersized for assigned buffer"); return; } } else { - if(new_size > allocation || !buffer) { + if (new_size > allocation || !buffer) { // DumpImgBuffer(); This is also done in AllocImgBuffer AllocImgBuffer(new_size); } @@ -675,110 +792,109 @@ void Image::Assign( const Image &image ) { colours = image.colours; subpixelorder = image.subpixelorder; size = new_size; + linesize = image.linesize; } - if(image.buffer != buffer) + update_function_pointers(); + if ( image.buffer != buffer ) (*fptr_imgbufcpy)(buffer, image.buffer, size); } -Image *Image::HighlightEdges( Rgb colour, unsigned int p_colours, unsigned int p_subpixelorder, const Box *limits ) { +Image *Image::HighlightEdges( + Rgb colour, + unsigned int p_colours, + unsigned int p_subpixelorder, + const Box *limits + ) { if ( colours != ZM_COLOUR_GRAY8 ) { Panic("Attempt to highlight image edges when colours = %d", colours); } /* Convert the colour's RGBA subpixel order into the image's subpixel order */ - colour = rgb_convert(colour,p_subpixelorder); + colour = rgb_convert(colour, p_subpixelorder); /* Create a new image of the target format */ - Image *high_image = new Image( width, height, p_colours, p_subpixelorder ); + Image *high_image = new Image(width, height, p_colours, p_subpixelorder); uint8_t* high_buff = high_image->WriteBuffer(width, height, p_colours, p_subpixelorder); /* 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++ ) - { - const uint8_t* p = buffer + (y * width) + lo_x; - uint8_t* phigh = high_buff + (y * width) + lo_x; - for ( unsigned int x = lo_x; x <= hi_x; x++, p++, phigh++ ) - { + if ( p_colours == ZM_COLOUR_GRAY8 ) { + for ( unsigned int y = lo_y; y <= hi_y; y++ ) { + const uint8_t* p = buffer + (y * linesize) + lo_x; + uint8_t* phigh = high_buff + (y * linesize) + lo_x; + for ( unsigned int x = lo_x; x <= hi_x; x++, p++, phigh++ ) { bool edge = false; - if ( *p ) - { + if ( *p ) { + edge = (x > 0 && !*(p-1)) || (x < (width-1) && !*(p+1)) || (y > 0 && !*(p-width)) || (y < (height-1) && !*(p+width)); +#if 0 if ( !edge && x > 0 && !*(p-1) ) edge = true; if ( !edge && x < (width-1) && !*(p+1) ) edge = true; if ( !edge && y > 0 && !*(p-width) ) edge = true; if ( !edge && y < (height-1) && !*(p+width) ) edge = true; +#endif } - if ( edge ) - { + if ( edge ) { *phigh = colour; } } } - } - else if ( p_colours == ZM_COLOUR_RGB24 ) - { - for ( unsigned int y = lo_y; y <= hi_y; y++ ) - { - const uint8_t* p = buffer + (y * width) + lo_x; - uint8_t* phigh = high_buff + (((y * width) + lo_x) * 3); - for ( unsigned int x = lo_x; x <= hi_x; x++, p++, phigh += 3 ) - { + } else if ( p_colours == ZM_COLOUR_RGB24 ) { + for ( unsigned int y = lo_y; y <= hi_y; y++ ) { + const uint8_t* p = buffer + (y * linesize) + lo_x; + uint8_t* phigh = high_buff + (((y * linesize) + lo_x) * 3); + for ( unsigned int x = lo_x; x <= hi_x; x++, p++, phigh += 3 ) { bool edge = false; - if ( *p ) - { + if ( *p ) { + edge = (x > 0 && !*(p-1)) || (x < (width-1) && !*(p+1)) || (y > 0 && !*(p-width)) || (y < (height-1) && !*(p+width)); +#if 0 if ( !edge && x > 0 && !*(p-1) ) edge = true; if ( !edge && x < (width-1) && !*(p+1) ) edge = true; if ( !edge && y > 0 && !*(p-width) ) edge = true; if ( !edge && y < (height-1) && !*(p+width) ) edge = true; +#endif } - if ( edge ) - { + if ( edge ) { RED_PTR_RGBA(phigh) = RED_VAL_RGBA(colour); GREEN_PTR_RGBA(phigh) = GREEN_VAL_RGBA(colour); BLUE_PTR_RGBA(phigh) = BLUE_VAL_RGBA(colour); } } } - } - else if ( p_colours == ZM_COLOUR_RGB32 ) - { - for ( unsigned int y = lo_y; y <= hi_y; y++ ) - { - const uint8_t* p = buffer + (y * width) + lo_x; - Rgb* phigh = (Rgb*)(high_buff + (((y * width) + lo_x) * 4)); - for ( unsigned int x = lo_x; x <= hi_x; x++, p++, phigh++ ) - { + } else if ( p_colours == ZM_COLOUR_RGB32 ) { + for ( unsigned int y = lo_y; y <= hi_y; y++ ) { + const uint8_t* p = buffer + (y * linesize) + lo_x; + Rgb* phigh = (Rgb*)(high_buff + (((y * linesize) + lo_x) * 4)); + for ( unsigned int x = lo_x; x <= hi_x; x++, p++, phigh++ ) { bool edge = false; - if ( *p ) - { + if ( *p ) { + edge = (x > 0 && !*(p-1)) || (x < (width-1) && !*(p+1)) || (y > 0 && !*(p-width)) || (y < (height-1) && !*(p+width)); +#if 0 if ( !edge && x > 0 && !*(p-1) ) edge = true; if ( !edge && x < (width-1) && !*(p+1) ) edge = true; if ( !edge && y > 0 && !*(p-width) ) edge = true; if ( !edge && y < (height-1) && !*(p+width) ) edge = true; +#endif } - if ( edge ) - { + if ( edge ) { *phigh = colour; } } } } - return( high_image ); + return high_image; } -bool Image::ReadRaw( const char *filename ) { +bool Image::ReadRaw(const char *filename) { FILE *infile; - if ( (infile = fopen( filename, "rb" )) == NULL ) { + if ( (infile = fopen(filename, "rb")) == nullptr ) { Error("Can't open %s: %s", filename, strerror(errno)); return false; } @@ -809,7 +925,7 @@ bool Image::ReadRaw( const char *filename ) { bool Image::WriteRaw(const char *filename) const { FILE *outfile; - if ( (outfile = fopen(filename, "wb")) == NULL ) { + if ( (outfile = fopen(filename, "wb")) == nullptr ) { Error("Can't open %s: %s", filename, strerror(errno)); return false; } @@ -825,132 +941,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")) == NULL ) { - 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 ) { - Error( "Unexpected colours when reading jpeg image: %d", colours ); - jpeg_abort_decompress(cinfo); + if ((readjpg_dcinfo->num_components != 1) && (readjpg_dcinfo->num_components != 3)) { + Error("Unexpected colours when reading jpeg image: %d", colours); + 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] == NULL ) { - 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 ) { - Debug(9,"Image dimensions differ. Old: %ux%u New: %ux%u",width,height,new_width,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; -#else - Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead."); -#endif + 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."); +#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) == NULL ) { + 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; /* pointer to a single row */ - int row_stride = width * colours; /* physical row width in buffer */ - while ( cinfo->output_scanline < cinfo->output_height ) { - row_pointer = &buffer[cinfo->output_scanline * row_stride]; - jpeg_read_scanlines(cinfo, &row_pointer, 1); + JSAMPROW row_pointer = buffer; + 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; @@ -959,72 +1066,77 @@ 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}); +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}); +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 { - return Image::WriteJpeg(filename, 0, timestamp); +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 { + // jpeg libs are not thread safe + std::unique_lock lck(jpeg_mutex); + + 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; + int quality = quality_override ? quality_override : config.jpeg_file_quality; - struct jpeg_compress_struct *cinfo = writejpg_ccinfo[quality]; - FILE *outfile = NULL; - static int raw_fd = 0; - bool need_create_comp = false; - raw_fd = 0; + jpeg_compress_struct *cinfo = writejpg_ccinfo[quality]; + FILE *outfile = nullptr; + 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); - need_create_comp = true; } - 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 == NULL) ? "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 ( need_create_comp ) - jpeg_create_compress(cinfo); - if ( !on_blocking_abort ) { - if ( (outfile = fopen(filename, "wb")) == NULL ) { - 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 == NULL ) { + if (outfile == nullptr) { close(raw_fd); return false; } @@ -1037,53 +1149,56 @@ bool Image::WriteJpeg(const char *filename, int quality_override, struct timeval 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_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 { - /* 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); @@ -1091,12 +1206,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 @@ -1105,150 +1220,148 @@ 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; /* pointer to a single row */ - int row_stride = cinfo->image_width * colours; /* physical row width in buffer */ - while ( cinfo->next_scanline < cinfo->image_height ) { - row_pointer = &buffer[cinfo->next_scanline * row_stride]; + JSAMPROW row_pointer = buffer; /* pointer to a single row */ + while (cinfo->next_scanline < cinfo->image_height) { jpeg_write_scanlines(cinfo, &row_pointer, 1); + row_pointer += linesize; } - jpeg_finish_compress(cinfo); - fclose(outfile); 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] == NULL ) { - 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) == NULL ) { + 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; /* pointer to a single row */ - int row_stride = width * colours; /* physical row width in buffer */ - while ( cinfo->output_scanline < cinfo->output_height ) { - row_pointer = &buffer[cinfo->output_scanline * row_stride]; - jpeg_read_scanlines(cinfo, &row_pointer, 1); + JSAMPROW row_pointer = buffer; /* pointer to a single row */ + 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; } @@ -1260,7 +1373,9 @@ bool Image::EncodeJpeg(JOCTET *outbuffer, int *outbuffer_size, int quality_overr return temp_image.EncodeJpeg(outbuffer, outbuffer_size, quality_override); } - int quality = quality_override?quality_override:config.jpeg_stream_quality; + std::unique_lock lck(jpeg_mutex); + + int quality = quality_override ? quality_override : config.jpeg_stream_quality; struct jpeg_compress_struct *cinfo = encodejpg_ccinfo[quality]; @@ -1277,7 +1392,7 @@ bool Image::EncodeJpeg(JOCTET *outbuffer, int *outbuffer_size, int quality_overr 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; @@ -1285,13 +1400,16 @@ bool Image::EncodeJpeg(JOCTET *outbuffer, int *outbuffer_size, int quality_overr case ZM_COLOUR_RGB32: #ifdef JCS_EXTENSIONS cinfo->input_components = 4; - if ( subpixelorder == ZM_SUBPIX_ORDER_BGRA ) { + 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("unknown subpixelorder %d", subpixelorder); /* Assume RGBA */ cinfo->in_color_space = JCS_EXT_RGBX; } @@ -1332,11 +1450,10 @@ cinfo->out_color_space = JCS_RGB; jpeg_start_compress(cinfo, TRUE); - JSAMPROW row_pointer; /* pointer to a single row */ - int row_stride = cinfo->image_width * colours; /* physical row width in buffer */ + JSAMPROW row_pointer = buffer; while ( cinfo->next_scanline < cinfo->image_height ) { - row_pointer = &buffer[cinfo->next_scanline * row_stride]; jpeg_write_scanlines(cinfo, &row_pointer, 1); + row_pointer += linesize; } jpeg_finish_compress(cinfo); @@ -1374,35 +1491,34 @@ bool Image::Crop( unsigned int lo_x, unsigned int lo_y, unsigned int hi_x, unsig unsigned int new_height = (hi_y-lo_y)+1; if ( lo_x > hi_x || lo_y > hi_y ) { - Error( "Invalid or reversed crop region %d,%d -> %d,%d", lo_x, lo_y, hi_x, hi_y ); - return( false ); - } - if ( hi_x > (width-1) || ( hi_y > (height-1) ) ) { - Error( "Attempting to crop outside image, %d,%d -> %d,%d not in %d,%d", lo_x, lo_y, hi_x, hi_y, width-1, height-1 ); + Error("Invalid or reversed crop region %d,%d -> %d,%d", lo_x, lo_y, hi_x, hi_y); return false; } - - if ( new_width == width && new_height == height ) { + if ( hi_x > (width-1) || ( hi_y > (height-1) ) ) { + Error("Attempting to crop outside image, %d,%d -> %d,%d not in %d,%d", + lo_x, lo_y, hi_x, hi_y, width-1, height-1); + return false; + } + if ( (new_width == width) && (new_height == height) ) { return true; } - unsigned int new_size = new_width*new_height*colours; + unsigned int new_stride = new_width * colours; + unsigned int new_size = new_stride * new_height; uint8_t *new_buffer = AllocBuffer(new_size); - unsigned int new_stride = new_width*colours; for ( unsigned int y = lo_y, ny = 0; y <= hi_y; y++, ny++ ) { - unsigned char *pbuf = &buffer[((y*width)+lo_x)*colours]; - unsigned char *pnbuf = &new_buffer[(ny*new_width)*colours]; - memcpy( pnbuf, pbuf, new_stride ); + unsigned char *pbuf = &buffer[((y*linesize)+(lo_x*colours))]; + unsigned char *pnbuf = &new_buffer[ny*new_stride]; + memcpy(pnbuf, pbuf, new_stride); } AssignDirect(new_width, new_height, colours, subpixelorder, new_buffer, new_size, ZM_BUFTYPE_ZM); - return true; } -bool Image::Crop( const Box &limits ) { - return Crop( limits.LoX(), limits.LoY(), limits.HiX(), limits.HiY() ); +bool Image::Crop(const Box &limits) { + return Crop(limits.Lo().x_, limits.Lo().y_, limits.Hi().x_, limits.Hi().y_); } /* Far from complete */ @@ -1414,7 +1530,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 */ @@ -1423,7 +1540,7 @@ void Image::Overlay( const Image &image ) { const uint8_t* psrc = image.buffer; uint8_t* pdest = buffer; - while( pdest < max_ptr ) { + while ( pdest < max_ptr ) { if ( *psrc ) { *pdest = *psrc; } @@ -1439,7 +1556,7 @@ void Image::Overlay( const Image &image ) { const uint8_t* psrc = image.buffer; uint8_t* pdest = buffer; - while( pdest < max_ptr ) { + while ( pdest < max_ptr ) { if ( RED_PTR_RGBA(psrc) || GREEN_PTR_RGBA(psrc) || BLUE_PTR_RGBA(psrc) ) { RED_PTR_RGBA(pdest) = RED_PTR_RGBA(psrc); GREEN_PTR_RGBA(pdest) = GREEN_PTR_RGBA(psrc); @@ -1450,7 +1567,7 @@ void Image::Overlay( const Image &image ) { } /* RGB32 ontop of grayscale - convert to same format first - complete */ - } else if( colours == ZM_COLOUR_GRAY8 && image.colours == ZM_COLOUR_RGB32 ) { + } else if ( colours == ZM_COLOUR_GRAY8 && image.colours == ZM_COLOUR_RGB32 ) { Colourise(image.colours, image.subpixelorder); const Rgb* const max_ptr = (Rgb*)(buffer+size); @@ -1459,7 +1576,7 @@ void Image::Overlay( const Image &image ) { if ( subpixelorder == ZM_SUBPIX_ORDER_RGBA || subpixelorder == ZM_SUBPIX_ORDER_BGRA ) { /* RGB\BGR\RGBA\BGRA subpixel order - Alpha byte is last */ - while (prdest < max_ptr) { + while ( prdest < max_ptr) { if ( RED_PTR_RGBA(prsrc) || GREEN_PTR_RGBA(prsrc) || BLUE_PTR_RGBA(prsrc) ) { *prdest = *prsrc; } @@ -1468,7 +1585,7 @@ void Image::Overlay( const Image &image ) { } } else { /* ABGR\ARGB subpixel order - Alpha byte is first */ - while (prdest < max_ptr) { + while ( prdest < max_ptr) { if ( RED_PTR_ABGR(prsrc) || GREEN_PTR_ABGR(prsrc) || BLUE_PTR_ABGR(prsrc) ) { *prdest = *prsrc; } @@ -1483,7 +1600,7 @@ void Image::Overlay( const Image &image ) { const uint8_t* psrc = image.buffer; uint8_t* pdest = buffer; - while( pdest < max_ptr ) { + while ( pdest < max_ptr ) { if ( *psrc ) { RED_PTR_RGBA(pdest) = GREEN_PTR_RGBA(pdest) = BLUE_PTR_RGBA(pdest) = *psrc; } @@ -1497,7 +1614,7 @@ void Image::Overlay( const Image &image ) { const uint8_t* psrc = image.buffer; uint8_t* pdest = buffer; - while( pdest < max_ptr ) { + while ( pdest < max_ptr ) { if ( RED_PTR_RGBA(psrc) || GREEN_PTR_RGBA(psrc) || BLUE_PTR_RGBA(psrc) ) { RED_PTR_RGBA(pdest) = RED_PTR_RGBA(psrc); GREEN_PTR_RGBA(pdest) = GREEN_PTR_RGBA(psrc); @@ -1571,15 +1688,15 @@ void Image::Overlay( const Image &image ) { } /* RGB32 compatible: complete */ -void Image::Overlay( const Image &image, unsigned int x, unsigned int y ) { +void Image::Overlay( const Image &image, const unsigned int lo_x, const unsigned int lo_y ) { if ( !(width < image.width || height < image.height) ) { Panic("Attempt to overlay image too big for destination, %dx%d > %dx%d", image.width, image.height, width, height ); } - if ( !(width < (x+image.width) || height < (y+image.height)) ) { + if ( !(width < (lo_x+image.width) || height < (lo_y+image.height)) ) { Panic("Attempt to overlay image outside of destination bounds, %dx%d @ %dx%d > %dx%d", - image.width, image.height, x, y, width, height ); + image.width, image.height, lo_x, lo_y, width, height ); } if ( !(colours == image.colours) ) { @@ -1587,10 +1704,8 @@ void Image::Overlay( const Image &image, unsigned int x, unsigned int y ) { colours, image.colours); } - unsigned int lo_x = x; - unsigned int lo_y = y; - unsigned int hi_x = (x+image.width)-1; - unsigned int hi_y = (y+image.height-1); + unsigned int hi_x = (lo_x+image.width)-1; + unsigned int hi_y = (lo_y+image.height-1); if ( colours == ZM_COLOUR_GRAY8 ) { const uint8_t *psrc = image.buffer; for ( unsigned int y = lo_y; y <= hi_y; y++ ) { @@ -1620,16 +1735,9 @@ void Image::Overlay( const Image &image, unsigned int x, unsigned int y ) { } else { Error("Overlay called with unexpected colours: %d", colours); } - -} +} // 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 ( !( width == image.width && height == image.height @@ -1643,28 +1751,30 @@ void Image::Blend( const Image &image, int transparency ) { if ( transparency <= 0 ) return; - new_buffer = AllocBuffer(size); + uint8_t* 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); } -Image *Image::Merge( unsigned int n_images, Image *images[] ) { +Image *Image::Merge(unsigned int n_images, Image *images[]) { if ( n_images == 1 ) return new Image(*images[0]); unsigned int width = images[0]->width; @@ -1693,7 +1803,7 @@ Image *Image::Merge( unsigned int n_images, Image *images[] ) { return result; } -Image *Image::Merge( unsigned int n_images, Image *images[], double weight ) { +Image *Image::Merge(unsigned int n_images, Image *images[], double weight) { if ( n_images == 1 ) return new Image(*images[0]); unsigned int width = images[0]->width; @@ -1706,7 +1816,7 @@ Image *Image::Merge( unsigned int n_images, Image *images[], double weight ) { } } - Image *result = new Image( *images[0] ); + Image *result = new Image(*images[0]); unsigned int size = result->size; double factor = 1.0*weight; for ( unsigned int i = 1; i < n_images; i++ ) { @@ -1735,7 +1845,7 @@ Image *Image::Highlight( unsigned int n_images, Image *images[], const Rgb thres } } - Image *result = new Image( width, height, images[0]->colours, images[0]->subpixelorder ); + Image *result = new Image(width, height, images[0]->colours, images[0]->subpixelorder); unsigned int size = result->size; for ( unsigned int c = 0; c < colours; c++ ) { unsigned int ref_colour_rgb = RGB_VAL(ref_colour,c); @@ -1761,13 +1871,7 @@ 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 - +void Image::Delta(const Image &image, Image* targetimage) const { 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); @@ -1775,15 +1879,15 @@ void Image::Delta( const Image &image, Image* targetimage) const { uint8_t *pdiff = targetimage->WriteBuffer(width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE); - if ( pdiff == NULL ) { + if ( pdiff == nullptr ) { Panic("Failed requesting writeable buffer for storing the delta image"); } #ifdef ZM_IMAGE_PROFILING - clock_gettime(CLOCK_THREAD_CPUTIME_ID,&start); + TimePoint start = std::chrono::steady_clock::now(); #endif - switch (colours) { + switch ( colours ) { case ZM_COLOUR_RGB24: if ( subpixelorder == ZM_SUBPIX_ORDER_BGR ) { /* BGR subpixel order */ @@ -1817,19 +1921,21 @@ 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 ) const { +const Vector2 Image::centreCoord(const char *text, int size = 1) const { int index = 0; int line_no = 0; - int text_len = strlen( text ); + int text_len = strlen(text); int line_len = 0; int max_line_len = 0; const char *line = text; @@ -1845,9 +1951,13 @@ const Coord Image::centreCoord( const char *text ) const { line = text+index; line_no++; } - int x = (width - (max_line_len * ZM_CHAR_WIDTH) ) / 2; - int y = (height - (line_no * LINE_HEIGHT) ) / 2; - return( Coord( x, y ) ); + + 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 {x, y}; } /* RGB32 compatible: complete */ @@ -1892,159 +2002,143 @@ void Image::MaskPrivacy( const unsigned char *p_bitmask, const Rgb pixel_colour } /* RGB32 compatible: complete */ -void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int size, const Rgb fg_colour, const Rgb bg_colour ) -{ - strncpy( text, p_text, sizeof(text)-1 ); +/* Bitmap decoding trick has been adopted from here: +https://lemire.me/blog/2018/02/21/iterating-over-set-bits-quickly/ +*/ +void Image::Annotate( + const std::string &text, + const Vector2 &coord, + const uint8 size, + const Rgb fg_colour, + const Rgb bg_colour) { + annotation_ = text; - unsigned int index = 0; - unsigned int line_no = 0; - unsigned int text_len = strlen( text ); - unsigned int line_len = 0; - const char *line = text; + const Rgb fg_rgb_col = rgb_convert(fg_colour, subpixelorder); + const Rgb bg_rgb_col = rgb_convert(bg_colour, subpixelorder); - const uint8_t fg_r_col = RED_VAL_RGBA(fg_colour); - const uint8_t fg_g_col = GREEN_VAL_RGBA(fg_colour); - const uint8_t fg_b_col = BLUE_VAL_RGBA(fg_colour); - const uint8_t fg_bw_col = fg_colour & 0xff; - const Rgb fg_rgb_col = rgb_convert(fg_colour,subpixelorder); - const bool fg_trans = (fg_colour == RGB_TRANSPARENT); + FontVariant const &font_variant = font.GetFontVariant(size - 1); + const uint16 char_width = font_variant.GetCharWidth(); + const uint16 char_height = font_variant.GetCharHeight(); - const uint8_t bg_r_col = RED_VAL_RGBA(bg_colour); - const uint8_t bg_g_col = GREEN_VAL_RGBA(bg_colour); - const uint8_t bg_b_col = BLUE_VAL_RGBA(bg_colour); - const uint8_t bg_bw_col = bg_colour & 0xff; - const Rgb bg_rgb_col = rgb_convert(bg_colour,subpixelorder); - const bool bg_trans = (bg_colour == RGB_TRANSPARENT); + std::vector lines = Split(annotation_, '\n'); + std::size_t max_line_length = 0; + for (const std::string &s : lines) { + max_line_length = std::max(max_line_length, s.size()); + } - int zm_text_bitmask = 0x80; - if (size == 2) - zm_text_bitmask = 0x8000; + uint32 x0_max = width - (max_line_length * char_width); + uint32 y0_max = height - (lines.size() * char_height); - while ( (index < text_len) && (line_len = strcspn( line, "\n" )) ) { + // Calculate initial coordinates of annotation so that everything is displayed even if the + // user set coordinates would prevent that. + uint32 x0 = zm::clamp(static_cast(coord.x_), 0u, x0_max); + uint32 y0 = zm::clamp(static_cast(coord.y_), 0u, y0_max); - unsigned int line_width = line_len * ZM_CHAR_WIDTH * size; + uint32 y = y0; + for (const std::string &line : lines) { + uint32 x = x0; - unsigned int lo_line_x = coord.X(); - unsigned int lo_line_y = coord.Y() + (line_no * LINE_HEIGHT * size); - - unsigned int min_line_x = 0; - unsigned int max_line_x = width - line_width; - unsigned int min_line_y = 0; - unsigned int max_line_y = height - (LINE_HEIGHT * size); - - if ( lo_line_x > max_line_x ) - lo_line_x = max_line_x; - if ( lo_line_x < min_line_x ) - lo_line_x = min_line_x; - if ( lo_line_y > max_line_y ) - lo_line_y = max_line_y; - if ( lo_line_y < min_line_y ) - lo_line_y = min_line_y; - - unsigned int hi_line_x = lo_line_x + line_width; - unsigned int hi_line_y = lo_line_y + (LINE_HEIGHT * size); - - // Clip anything that runs off the right of the screen - if ( hi_line_x > width ) - hi_line_x = width; - if ( hi_line_y > height ) - hi_line_y = height; - - if ( colours == ZM_COLOUR_GRAY8 ) { - unsigned char *ptr = &buffer[(lo_line_y*width)+lo_line_x]; - for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += width ) { - unsigned char *temp_ptr = ptr; - for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) { - int f; - if (size == 2) - f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r]; - else - f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r]; - for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) { - if ( f & (zm_text_bitmask >> i) ) { - if ( !fg_trans ) - *temp_ptr = fg_bw_col; - } else if ( !bg_trans ) { - *temp_ptr = bg_bw_col; - } + 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)); } + + while (cp_row != 0) { + uint32 column_idx = char_width - __builtin_ctzll(cp_row) + font_variant.GetCharPadding(); + *(ptr + column_idx) = fg_colour & 0xff; + cp_row = cp_row & (cp_row - 1); + } + ptr += width; + } + ptr -= (width * char_height); + ptr += char_width; + x += char_width; + if (x >= width) { + break; } } - } else if ( colours == ZM_COLOUR_RGB24 ) { - unsigned int wc = width * colours; + } else if (colours == ZM_COLOUR_RGB24) { + constexpr uint8 bytesPerPixel = 3; + uint8 *ptr = &buffer[((y * width) + x0) * bytesPerPixel]; - unsigned char *ptr = &buffer[((lo_line_y*width)+lo_line_x)*colours]; - for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += wc ) { - unsigned char *temp_ptr = ptr; - for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) { - int f; - if (size == 2) - f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r]; - else - f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r]; - for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr += colours ) { - if ( f & (zm_text_bitmask >> i) ) { - if ( !fg_trans ) { - RED_PTR_RGBA(temp_ptr) = fg_r_col; - GREEN_PTR_RGBA(temp_ptr) = fg_g_col; - BLUE_PTR_RGBA(temp_ptr) = fg_b_col; - } - } else if ( !bg_trans ) { - RED_PTR_RGBA(temp_ptr) = bg_r_col; - GREEN_PTR_RGBA(temp_ptr) = bg_g_col; - BLUE_PTR_RGBA(temp_ptr) = bg_b_col; + for (char c : line) { + for (uint64 cp_row : font_variant.GetCodepoint(c)) { + if (bg_colour != kRGBTransparent) { + for (uint16 i = 0; i < char_width; i++) { // We need to set individual r,g,b components + uint8 *colour_ptr = ptr + (i * bytesPerPixel); + RED_PTR_RGBA(colour_ptr) = RED_VAL_RGBA(bg_colour); + GREEN_PTR_RGBA(colour_ptr) = GREEN_VAL_RGBA(bg_colour); + BLUE_PTR_RGBA(colour_ptr) = BLUE_VAL_RGBA(bg_colour); } } - } - } - } else if ( colours == ZM_COLOUR_RGB32 ) { - unsigned int wc = width * colours; - uint8_t *ptr = &buffer[((lo_line_y*width)+lo_line_x)<<2]; - for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += wc ) { - Rgb* temp_ptr = (Rgb*)ptr; - for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) { - int f; - if (size == 2) - f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r]; - else - f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r]; - for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) { - if ( f & (zm_text_bitmask >> i) ) { - if ( !fg_trans ) { - *temp_ptr = fg_rgb_col; - } - } else if ( !bg_trans ) { - *temp_ptr = bg_rgb_col; - } + while (cp_row != 0) { + uint32 column_idx = char_width - __builtin_ctzll(cp_row) + font_variant.GetCharPadding(); + uint8 *colour_ptr = ptr + (column_idx * bytesPerPixel); + RED_PTR_RGBA(colour_ptr) = RED_VAL_RGBA(fg_colour); + GREEN_PTR_RGBA(colour_ptr) = GREEN_VAL_RGBA(fg_colour); + BLUE_PTR_RGBA(colour_ptr) = BLUE_VAL_RGBA(fg_colour); + cp_row = cp_row & (cp_row - 1); } + ptr += width * bytesPerPixel; + } + ptr -= (width * char_height * bytesPerPixel); + ptr += char_width * bytesPerPixel; + x += char_width; + if (x >= width) { + break; } } + } else if (colours == ZM_COLOUR_RGB32) { + constexpr uint8 bytesPerPixel = 4; + Rgb *ptr = reinterpret_cast(&buffer[((y * width) + x0) * bytesPerPixel]); + 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); + } + + while (cp_row != 0) { + uint32 column_idx = char_width - __builtin_ctzll(cp_row) + font_variant.GetCharPadding(); + *(ptr + column_idx) = fg_rgb_col; + cp_row = cp_row & (cp_row - 1); + } + ptr += width; + } + ptr -= (width * char_height); + ptr += char_width; + x += char_width; + if (x >= width) { + break; + } + } } else { - Panic("Annotate called with unexpected colours: %d",colours); + 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); } } @@ -2112,7 +2206,7 @@ void Image::DeColourise() { subpixelorder = ZM_SUBPIX_ORDER_NONE; size = width * height; - if ( colours == ZM_COLOUR_RGB32 && config.cpu_extensions && sseversion >= 35 ) { + if ( colours == ZM_COLOUR_RGB32 && config.cpu_extensions && sse_version >= 35 ) { /* Use SSSE3 functions */ switch (subpixelorder) { case ZM_SUBPIX_ORDER_BGRA: @@ -2201,10 +2295,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]; @@ -2239,17 +2333,17 @@ void Image::Fill( Rgb colour, int density, const Box *limits ) { if ( density <= 1 ) return Fill(colour,limits); - 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); + 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]; @@ -2289,26 +2383,25 @@ 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; double grad; - //Debug( 9, "dx: %.2lf, dy: %.2lf", dx, dy ); if ( fabs(dx) <= fabs(dy) ) { - //Debug( 9, "dx <= dy" ); if ( y1 != y2 ) grad = dx/dy; else @@ -2318,9 +2411,7 @@ void Image::Outline( Rgb colour, const Polygon &polygon ) { int y, yinc = (y1 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; } } - qsort( active_edges, n_active_edges, sizeof(*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_RGB24) { + constexpr uint8 bytesPerPixel = 3; + uint8 *ptr = &buffer[((scan_line * width) + lo_x) * bytesPerPixel]; + + 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 ) { - 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; + } 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; } } } } } - 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 ); + + scan_line++; + } } -void Image::Fill( Rgb colour, const Polygon &polygon ) { - Fill( colour, 1, polygon ); -} - -/* RGB32 compatible: complete */ -void Image::Rotate( int angle ) { - +void Image::Rotate(int angle) { angle %= 360; - if ( !angle ) { + if ( !angle || angle%90 ) { return; } - if ( angle%90 ) { - return; - } - unsigned int new_height = height; unsigned int new_width = width; uint8_t* rotate_buffer = AllocBuffer(size); - switch( angle ) { + switch ( angle ) { case 90 : { new_height = width; @@ -2527,17 +2591,17 @@ void Image::Rotate( int angle ) { } } else if ( colours == ZM_COLOUR_RGB32 ) { Rgb* s_rptr = (Rgb*)s_ptr; - for ( unsigned int i = new_width; i > 0; i-- ) { + for ( unsigned int i = new_width; i; i-- ) { Rgb* d_rptr = (Rgb*)(rotate_buffer+((i-1)<<2)); - for ( unsigned int j = new_height; j > 0; j-- ) { + for ( unsigned int j = new_height; j; j-- ) { *d_rptr = *s_rptr++; d_rptr += new_width; } } } else /* Assume RGB24 */ { - for ( unsigned int i = new_width; i > 0; i-- ) { + for ( unsigned int i = new_width; i; i-- ) { unsigned char *d_ptr = rotate_buffer+((i-1)*3); - for ( unsigned int j = new_height; j > 0; j-- ) { + for ( unsigned int j = new_height; j; j-- ) { *d_ptr = *s_ptr++; *(d_ptr+1) = *s_ptr++; *(d_ptr+2) = *s_ptr++; @@ -2616,8 +2680,8 @@ void Image::Rotate( int angle ) { } } - AssignDirect( new_width, new_height, colours, subpixelorder, rotate_buffer, size, ZM_BUFTYPE_ZM); -} + AssignDirect(new_width, new_height, colours, subpixelorder, rotate_buffer, size, ZM_BUFTYPE_ZM); +} // void Image::Rotate(int angle) /* RGB32 compatible: complete */ void Image::Flip( bool leftright ) { @@ -2666,20 +2730,19 @@ void Image::Flip( bool leftright ) { unsigned char *s_ptr = buffer+(height*line_bytes); unsigned char *d_ptr = flip_buffer; - while( s_ptr > buffer ) { + while ( s_ptr > buffer ) { s_ptr -= line_bytes; - memcpy( d_ptr, s_ptr, line_bytes ); + memcpy(d_ptr, s_ptr, line_bytes); d_ptr += line_bytes; } } - AssignDirect( width, height, colours, subpixelorder, flip_buffer, size, ZM_BUFTYPE_ZM); - + AssignDirect(width, height, colours, subpixelorder, flip_buffer, size, ZM_BUFTYPE_ZM); } -void Image::Scale( unsigned int factor ) { +void Image::Scale(const unsigned int factor) { if ( !factor ) { - Error( "Bogus scale factor %d found", factor ); + Error("Bogus scale factor %d found", factor); return; } if ( factor == ZM_SCALE_BASE ) { @@ -2689,6 +2752,7 @@ void Image::Scale( unsigned int factor ) { unsigned int new_width = (width*factor)/ZM_SCALE_BASE; unsigned int new_height = (height*factor)/ZM_SCALE_BASE; + // Why larger than we need? size_t scale_buffer_size = (new_width+1) * (new_height+1) * colours; uint8_t* scale_buffer = AllocBuffer(scale_buffer_size); @@ -2700,15 +2764,13 @@ void Image::Scale( unsigned int factor ) { unsigned int h_count = ZM_SCALE_BASE/2; unsigned int last_h_index = 0; unsigned int last_w_index = 0; - unsigned int h_index; for ( unsigned int y = 0; y < height; y++ ) { unsigned char *ps = &buffer[y*wc]; unsigned int w_count = ZM_SCALE_BASE/2; - unsigned int w_index; last_w_index = 0; for ( unsigned int x = 0; x < width; x++ ) { w_count += factor; - w_index = w_count/ZM_SCALE_BASE; + unsigned int w_index = w_count/ZM_SCALE_BASE; for (unsigned int f = last_w_index; f < w_index; f++ ) { for ( unsigned int c = 0; c < colours; c++ ) { *pd++ = *(ps+c); @@ -2718,34 +2780,31 @@ void Image::Scale( unsigned int factor ) { last_w_index = w_index; } h_count += factor; - h_index = h_count/ZM_SCALE_BASE; + unsigned int h_index = h_count/ZM_SCALE_BASE; for ( unsigned int f = last_h_index+1; f < h_index; f++ ) { - memcpy( pd, pd-nwc, nwc ); + memcpy(pd, pd-nwc, nwc); pd += nwc; } last_h_index = h_index; - } + } // end foreach line new_width = last_w_index; new_height = last_h_index; } else { unsigned char *pd = scale_buffer; unsigned int wc = width*colours; - unsigned int xstart = factor/2; - unsigned int ystart = factor/2; - unsigned int h_count = ystart; + unsigned int h_count = factor/2; unsigned int last_h_index = 0; unsigned int last_w_index = 0; - unsigned int h_index; - for ( unsigned int y = 0; y < (unsigned int)height; y++ ) { + for ( unsigned int y = 0; y < height; y++ ) { h_count += factor; - h_index = h_count/ZM_SCALE_BASE; + unsigned int h_index = h_count/ZM_SCALE_BASE; if ( h_index > last_h_index ) { - unsigned int w_count = xstart; + unsigned int w_count = factor/2; unsigned int w_index; last_w_index = 0; unsigned char *ps = &buffer[y*wc]; - for ( unsigned int x = 0; x < (unsigned int)width; x++ ) { + for ( unsigned int x = 0; x < width; x++ ) { w_count += factor; w_index = w_count/ZM_SCALE_BASE; @@ -2763,14 +2822,13 @@ void Image::Scale( unsigned int factor ) { } new_width = last_w_index; new_height = last_h_index; - } - - AssignDirect( new_width, new_height, colours, subpixelorder, scale_buffer, scale_buffer_size, ZM_BUFTYPE_ZM); - + } // end foreach line + AssignDirect(new_width, new_height, colours, subpixelorder, scale_buffer, scale_buffer_size, ZM_BUFTYPE_ZM); } void Image::Deinterlace_Discard() { /* Simple deinterlacing. Copy the even lines into the odd lines */ + // ICON: These can be drastically improved. But who cares? if ( colours == ZM_COLOUR_GRAY8 ) { const uint8_t *psrc; @@ -2807,7 +2865,6 @@ void Image::Deinterlace_Discard() { } else { Error("Deinterlace called with unexpected colours: %d", colours); } - } void Image::Deinterlace_Linear() { @@ -3054,9 +3111,9 @@ __attribute__((noinline,__target__("sse2"))) #endif void sse2_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) { #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - static uint32_t divider = 0; - static uint32_t clearmask = 0; static double current_blendpercent = 0.0; + static uint32_t clearmask = 0; + static uint32_t divider = 0; if ( current_blendpercent != blendpercent ) { /* Attempt to match the blending percent to one of the possible values */ @@ -3257,10 +3314,10 @@ void neon32_armv7_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* r __attribute__((noinline)) void neon64_armv8_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) { #if (defined(__aarch64__) && !defined(ZM_STRIP_NEON)) - static int8_t divider = 0; static double current_blendpercent = 0.0; + static int8_t divider = 0; - if(current_blendpercent != blendpercent) { + if (current_blendpercent != blendpercent) { /* Attempt to match the blending percent to one of the possible values */ if(blendpercent < 2.34375) { // 1.5625% blending @@ -3340,6 +3397,7 @@ __attribute__((noinline)) void neon64_armv8_fastblend(const uint8_t* col1, const } __attribute__((noinline)) void std_blend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) { + Warning("Using slow std_blend"); double divide = blendpercent / 100.0; double opacity = 1.0 - divide; const uint8_t* const max_ptr = result + count; @@ -3445,7 +3503,7 @@ __attribute__((noinline)) void fast_delta8_bgr(const uint8_t* col1, const uint8_ int r,g,b; const uint8_t* const max_ptr = result + count; - while(result < max_ptr) { + while (result < max_ptr) { b = abs(col1[0] - col2[0]); g = abs(col1[1] - col2[1]); r = abs(col1[2] - col2[2]); @@ -3474,7 +3532,7 @@ __attribute__((noinline)) void std_delta8_bgr(const uint8_t* col1, const uint8_t int r,g,b; const uint8_t* const max_ptr = result + count; - while(result < max_ptr) { + while (result < max_ptr) { b = abs(col1[0] - col2[0]); g = abs(col1[1] - col2[1]); r = abs(col1[2] - col2[2]); @@ -3492,7 +3550,7 @@ __attribute__((noinline)) void fast_delta8_rgba(const uint8_t* col1, const uint8 int r,g,b; const uint8_t* const max_ptr = result + count; - while(result < max_ptr) { + while (result < max_ptr) { r = abs(col1[0] - col2[0]); g = abs(col1[1] - col2[1]); b = abs(col1[2] - col2[2]); @@ -3521,7 +3579,7 @@ __attribute__((noinline)) void std_delta8_rgba(const uint8_t* col1, const uint8_ int r,g,b; const uint8_t* const max_ptr = result + count; - while(result < max_ptr) { + while (result < max_ptr) { r = abs(col1[0] - col2[0]); g = abs(col1[1] - col2[1]); b = abs(col1[2] - col2[2]); @@ -3539,7 +3597,7 @@ __attribute__((noinline)) void fast_delta8_bgra(const uint8_t* col1, const uint8 int r,g,b; const uint8_t* const max_ptr = result + count; - while(result < max_ptr) { + while (result < max_ptr) { b = abs(col1[0] - col2[0]); g = abs(col1[1] - col2[1]); r = abs(col1[2] - col2[2]); @@ -3567,7 +3625,7 @@ __attribute__((noinline)) void std_delta8_bgra(const uint8_t* col1, const uint8_ int r,g,b; const uint8_t* const max_ptr = result + count; - while(result < max_ptr) { + while (result < max_ptr) { b = abs(col1[0] - col2[0]); g = abs(col1[1] - col2[1]); r = abs(col1[2] - col2[2]); @@ -3585,7 +3643,7 @@ __attribute__((noinline)) void fast_delta8_argb(const uint8_t* col1, const uint8 int r,g,b; const uint8_t* const max_ptr = result + count; - while(result < max_ptr) { + while (result < max_ptr) { r = abs(col1[1] - col2[1]); g = abs(col1[2] - col2[2]); b = abs(col1[3] - col2[3]); @@ -3608,12 +3666,13 @@ __attribute__((noinline)) void fast_delta8_argb(const uint8_t* col1, const uint8 result += 4; } } + __attribute__((noinline)) void std_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { /* Loop unrolling is used to work on 16 bytes (4 rgb32 pixels) at a time */ int r,g,b; const uint8_t* const max_ptr = result + count; - while(result < max_ptr) { + while (result < max_ptr) { r = abs(col1[1] - col2[1]); g = abs(col1[2] - col2[2]); b = abs(col1[3] - col2[3]); @@ -3631,7 +3690,7 @@ __attribute__((noinline)) void fast_delta8_abgr(const uint8_t* col1, const uint8 int r,g,b; const uint8_t* const max_ptr = result + count; - while(result < max_ptr) { + while (result < max_ptr) { b = abs(col1[1] - col2[1]); g = abs(col1[2] - col2[2]); r = abs(col1[3] - col2[3]); @@ -3658,7 +3717,7 @@ __attribute__((noinline)) void std_delta8_abgr(const uint8_t* col1, const uint8_ int r,g,b; const uint8_t* const max_ptr = result + count; - while(result < max_ptr) { + while (result < max_ptr) { b = abs(col1[1] - col2[1]); g = abs(col1[2] - col2[2]); r = abs(col1[3] - col2[3]); @@ -4639,8 +4698,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]; @@ -5189,3 +5248,5 @@ __attribute__((noinline)) void std_deinterlace_4field_abgr(uint8_t* col1, uint8_ pncurrent += 4; } } + + diff --git a/src/zm_image.h b/src/zm_image.h index 6b2448c67..8bcb92c4c 100644 --- a/src/zm_image.h +++ b/src/zm_image.h @@ -20,26 +20,21 @@ #ifndef ZM_IMAGE_H #define ZM_IMAGE_H -#include "zm.h" -extern "C" { -#include "zm_jpeg.h" -} -#include "zm_rgb.h" -#include "zm_coord.h" -#include "zm_box.h" -#include "zm_poly.h" -#include "zm_mem_utils.h" -#include "zm_utils.h" - -class Image; #include "zm_ffmpeg.h" - -#include +#include "zm_jpeg.h" +#include "zm_logger.h" +#include "zm_mem_utils.h" +#include "zm_rgb.h" +#include "zm_time.h" +#include "zm_vector2.h" #if HAVE_ZLIB_H #include #endif // HAVE_ZLIB_H +class Box; +class Polygon; + #define ZM_BUFTYPE_DONTFREE 0 #define ZM_BUFTYPE_MALLOC 1 #define ZM_BUFTYPE_NEW 2 @@ -57,14 +52,14 @@ extern imgbufcpy_fptr_t fptr_imgbufcpy; /* Should be called from Image class functions */ inline static uint8_t* AllocBuffer(size_t p_bufsize) { uint8_t* buffer = (uint8_t*)zm_mallocaligned(64, p_bufsize); - if ( buffer == NULL ) + if ( buffer == nullptr ) Fatal("Memory allocation failed: %s", strerror(errno)); return buffer; } inline static void DumpBuffer(uint8_t* buffer, int buffertype) { - if ( buffer && buffertype != ZM_BUFTYPE_DONTFREE ) { + if ( buffer && (buffertype != ZM_BUFTYPE_DONTFREE) ) { if ( buffertype == ZM_BUFTYPE_ZM ) { zm_freealigned(buffer); } else if ( buffertype == ZM_BUFTYPE_MALLOC ) { @@ -99,180 +94,238 @@ class Image { blend_fptr_t blend; void update_function_pointers(); -protected: - struct Edge { - int min_y; - int max_y; - double min_x; - double _1_m; + protected: + inline void AllocImgBuffer(size_t p_bufsize) { + if ( buffer ) + DumpImgBuffer(); - 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) ); - } - }; - - inline void DumpImgBuffer() { - DumpBuffer(buffer,buffertype); - buffer = NULL; - allocation = 0; - } - - inline void AllocImgBuffer(size_t p_bufsize) { - if ( buffer ) - DumpImgBuffer(); - - buffer = AllocBuffer(p_bufsize); - buffertype = ZM_BUFTYPE_ZM; - allocation = p_bufsize; - } + 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 }; + 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; + 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 height; - unsigned int pixels; - unsigned int colours; - unsigned int size; - unsigned int subpixelorder; - unsigned long allocation; - uint8_t *buffer; - int buffertype; /* 0=not ours, no need to call free(), 1=malloc() buffer, 2=new buffer */ - int holdbuffer; /* Hold the buffer instead of replacing it with new one */ - char text[1024]; + 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_; -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); - explicit Image( const Image &p_image ); - explicit Image( const AVFrame *frame ); - ~Image(); - static void Initialise(); - static void Deinitialise(); + 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); - inline unsigned int Width() const { return width; } - inline unsigned int Height() const { return height; } - inline unsigned int Pixels() const { return pixels; } - inline unsigned int Colours() const { return colours; } - inline unsigned int SubpixelOrder() const { return subpixelorder; } - inline unsigned int Size() const { return size; } - - /* Internal buffer should not be modified from functions outside of this class */ - inline const uint8_t* Buffer() const { return buffer; } - inline const uint8_t* Buffer( unsigned int x, unsigned int y= 0 ) const { return &buffer[colours*((y*width)+x)]; } - /* Request writeable buffer */ - uint8_t* WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder); - - inline int IsBufferHeld() const { return holdbuffer; } - inline void HoldBuffer(int tohold) { holdbuffer = tohold; } - - inline void Empty() { - if ( !holdbuffer ) - DumpImgBuffer(); + ~Image(); - width = height = colours = size = pixels = subpixelorder = 0; - } - - void Assign( unsigned int p_width, unsigned int p_height, unsigned int p_colours, unsigned int p_subpixelorder, const uint8_t* new_buffer, const size_t buffer_size); - void Assign( const Image &image ); - void AssignDirect( const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, uint8_t *new_buffer, const size_t buffer_size, const int p_buffertype); + static void Initialise(); + static void Deinitialise(); - inline void CopyBuffer( const Image &image ) { - Assign(image); - } - inline Image &operator=( const Image &image ) { - Assign(image); - return *this; - } - inline Image &operator=( const unsigned char *new_buffer ) { - (*fptr_imgbufcpy)(buffer, new_buffer, size); - return *this; - } + inline void DumpImgBuffer() { + if (buffertype != ZM_BUFTYPE_DONTFREE) + DumpBuffer(buffer, buffertype); + buffertype = ZM_BUFTYPE_DONTFREE; + buffer = nullptr; + allocation = 0; + } + inline unsigned int Width() const { return width; } + inline unsigned int LineSize() const { return linesize; } + inline unsigned int Height() const { return height; } + inline unsigned int Pixels() const { return pixels; } + inline unsigned int Colours() const { return colours; } + inline unsigned int SubpixelOrder() const { return subpixelorder; } + inline unsigned int Size() const { return size; } - bool ReadRaw( const char *filename ); - bool WriteRaw( const char *filename ) const; + inline AVPixelFormat AVPixFormat() { + if ( colours == ZM_COLOUR_RGB32 ) { + return AV_PIX_FMT_RGBA; + } else if ( colours == ZM_COLOUR_RGB24 ) { + if ( subpixelorder == ZM_SUBPIX_ORDER_BGR){ + return AV_PIX_FMT_BGR24; + } else { + return AV_PIX_FMT_RGB24; + } + } else if ( colours == ZM_COLOUR_GRAY8 ) { + return AV_PIX_FMT_GRAY8; + } else { + Error("Unknown colours (%d)",colours); + return AV_PIX_FMT_RGBA; + } + } - bool ReadJpeg( const char *filename, unsigned int p_colours, unsigned int p_subpixelorder); + 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]; } - 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; - + /* Request writeable buffer */ + uint8_t* WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder); + // Is only acceptable on a pre-allocated buffer + uint8_t* WriteBuffer() { return holdbuffer ? buffer : nullptr; }; - bool 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 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[], const Rgb threshold=RGB_BLACK, const Rgb ref_colour=RGB_RED ); - //Image *Delta( const Image &image ) const; - void Delta( const Image &image, Image* targetimage) const; + void Overlay(const Image &image); + void Overlay(const Image &image, unsigned int x, unsigned int y); + void Blend(const Image &image, int transparency=12); + static Image *Merge( unsigned int n_images, Image *images[] ); + static Image *Merge( unsigned int n_images, Image *images[], double weight ); + static Image *Highlight(unsigned int n_images, Image *images[], Rgb threshold = kRGBBlack, Rgb ref_colour = kRGBRed); + //Image *Delta( const Image &image ) const; + void Delta( const Image &image, Image* targetimage) const; - const Coord centreCoord( const char *text ) const; - void MaskPrivacy( const unsigned char *p_bitmask, const Rgb pixel_colour=0x00222222 ); - void Annotate( const char *p_text, const Coord &coord, const unsigned int size=1, const Rgb fg_colour=RGB_WHITE, const Rgb bg_colour=RGB_BLACK ); - Image *HighlightEdges( Rgb colour, unsigned int p_colours, unsigned int p_subpixelorder, const Box *limits=0 ); - //Image *HighlightEdges( Rgb colour, const Polygon &polygon ); - void Timestamp( const char *label, const time_t when, const Coord &coord, const int size ); - void Colourise(const unsigned int p_reqcolours, const unsigned int p_reqsubpixelorder); - void DeColourise(); + const 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 8e8899227..a71bef48e 100644 --- a/src/zm_jpeg.cpp +++ b/src/zm_jpeg.cpp @@ -18,76 +18,66 @@ */ #include "zm_jpeg.h" + #include "zm_logger.h" -#include - /* Overridden error handlers, mostly for decompression */ -extern "C" -{ +extern "C" { #define MAX_JPEG_ERRS 25 static int jpeg_err_count = 0; -void zm_jpeg_error_silent( j_common_ptr cinfo ){ +void zm_jpeg_error_silent(j_common_ptr cinfo) { zm_error_ptr zmerr = (zm_error_ptr)cinfo->err; - longjmp( zmerr->setjmp_buffer, 1 ); -} -void zm_jpeg_emit_silence( j_common_ptr cinfo, int msg_level ){ + longjmp(zmerr->setjmp_buffer, 1); } -void zm_jpeg_error_exit( j_common_ptr cinfo ) -{ - static char buffer[JMSG_LENGTH_MAX]; - zm_error_ptr zmerr = (zm_error_ptr)cinfo->err; +void zm_jpeg_emit_silence(j_common_ptr cinfo, int msg_level) { +} - (zmerr->pub.format_message)( cinfo, buffer ); +void zm_jpeg_error_exit(j_common_ptr cinfo) { + zm_error_ptr zmerr = (zm_error_ptr) cinfo->err; - Error( "%s", buffer ); - if ( ++jpeg_err_count == MAX_JPEG_ERRS ) - { - Fatal( "Maximum number (%d) of JPEG errors reached, exiting", jpeg_err_count ); + char buffer[JMSG_LENGTH_MAX]; + zmerr->pub.format_message(cinfo, buffer); + + Error("%s", buffer); + if (++jpeg_err_count == MAX_JPEG_ERRS) { + Fatal("Maximum number (%d) of JPEG errors reached, exiting", jpeg_err_count); } - longjmp( zmerr->setjmp_buffer, 1 ); + longjmp(zmerr->setjmp_buffer, 1); } -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; +void zm_jpeg_emit_message(j_common_ptr cinfo, int msg_level) { + char buffer[JMSG_LENGTH_MAX]; + zm_error_ptr zmerr = (zm_error_ptr) cinfo->err; - if ( msg_level < 0 ) - { + if (msg_level < 0) { /* It's a warning message. Since corrupt files may generate many warnings, * the policy implemented here is to show only the first warning, * unless trace_level >= 3. */ - if ( zmerr->pub.num_warnings == 0 || zmerr->pub.trace_level >= 3 ) - { - (zmerr->pub.format_message)( cinfo, buffer ); - if (!strstr(buffer, "Corrupt JPEG data:")) - Warning( "%s", buffer ); + if (zmerr->pub.num_warnings == 0 || zmerr->pub.trace_level >= 3) { + zmerr->pub.format_message(cinfo, buffer); + if (!strstr(buffer, "Corrupt JPEG data:")) + Warning("%s", buffer); } /* Always count warnings in num_warnings. */ zmerr->pub.num_warnings++; - } - else - { + } else { /* It's a trace message. Show it if trace_level >= msg_level. */ - if ( zmerr->pub.trace_level >= msg_level ) - { - (zmerr->pub.format_message)( cinfo, buffer ); - Debug( msg_level, "%s", buffer ); + if (zmerr->pub.trace_level >= msg_level) { + zmerr->pub.format_message(cinfo, buffer); + Debug(msg_level, "%s", buffer); } } } /* Expanded data destination object for memory */ -typedef struct -{ +typedef struct { struct jpeg_destination_mgr pub; /* public fields */ JOCTET *outbuffer; /* target buffer */ @@ -104,8 +94,7 @@ typedef mem_destination_mgr * mem_dest_ptr; * before any data is actually written. */ -static void init_destination (j_compress_ptr cinfo) -{ +static void init_destination (j_compress_ptr cinfo) { mem_dest_ptr dest = (mem_dest_ptr) cinfo->dest; /* Allocate the output buffer --- it will be released when done with image */ @@ -117,7 +106,6 @@ static void init_destination (j_compress_ptr cinfo) *(dest->outbuffer_size) = 0; } - /* * Empty the output buffer --- called whenever buffer fills up. * @@ -141,17 +129,16 @@ static void init_destination (j_compress_ptr cinfo) * write it out when emptying the buffer externally. */ -static boolean empty_output_buffer (j_compress_ptr cinfo) -{ +static boolean empty_output_buffer(j_compress_ptr cinfo) { mem_dest_ptr dest = (mem_dest_ptr) cinfo->dest; - memcpy( dest->outbuffer+*(dest->outbuffer_size), dest->buffer, OUTPUT_BUF_SIZE ); + memcpy(dest->outbuffer+*(dest->outbuffer_size), dest->buffer, OUTPUT_BUF_SIZE); *(dest->outbuffer_size) += OUTPUT_BUF_SIZE; dest->pub.next_output_byte = dest->buffer; dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; - return( TRUE ); + return TRUE; } /* @@ -163,14 +150,12 @@ static boolean empty_output_buffer (j_compress_ptr cinfo) * for error exit. */ -static void term_destination (j_compress_ptr cinfo) -{ +static void term_destination(j_compress_ptr cinfo) { mem_dest_ptr dest = (mem_dest_ptr) cinfo->dest; size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer; - if ( datacount > 0 ) - { - memcpy( dest->outbuffer+*(dest->outbuffer_size), dest->buffer, datacount ); + if ( datacount > 0 ) { + memcpy(dest->outbuffer+*(dest->outbuffer_size), dest->buffer, datacount); *(dest->outbuffer_size) += datacount; } } @@ -182,8 +167,7 @@ static void term_destination (j_compress_ptr cinfo) * for closing it after finishing compression. */ -void zm_jpeg_mem_dest (j_compress_ptr cinfo, JOCTET *outbuffer, int *outbuffer_size ) -{ +void zm_jpeg_mem_dest(j_compress_ptr cinfo, JOCTET *outbuffer, int *outbuffer_size) { mem_dest_ptr dest; /* The destination object is made permanent so that multiple JPEG images @@ -192,10 +176,10 @@ void zm_jpeg_mem_dest (j_compress_ptr cinfo, JOCTET *outbuffer, int *outbuffer_s * manager serially with the same JPEG object, because their private object * sizes may be different. Caveat programmer. */ - if ( cinfo->dest == NULL ) - { + if ( cinfo->dest == nullptr ) { /* first time for this JPEG object? */ - cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, SIZEOF(mem_destination_mgr)); + cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_PERMANENT, SIZEOF(mem_destination_mgr)); } dest = (mem_dest_ptr) cinfo->dest; @@ -208,8 +192,7 @@ void zm_jpeg_mem_dest (j_compress_ptr cinfo, JOCTET *outbuffer, int *outbuffer_s /* Expanded data source object for memory input */ -typedef struct -{ +typedef struct { struct jpeg_source_mgr pub; /* public fields */ JOCTET * inbuffer; /* source stream */ @@ -229,8 +212,7 @@ typedef mem_source_mgr * mem_src_ptr; * before any data is actually read. */ -static void init_source (j_decompress_ptr cinfo) -{ +static void init_source (j_decompress_ptr cinfo) { mem_src_ptr src = (mem_src_ptr) cinfo->src; /* We reset the empty-input-file flag for each image, @@ -275,12 +257,11 @@ static void init_source (j_decompress_ptr cinfo) * the front of the buffer rather than discarding it. */ -static boolean fill_input_buffer (j_decompress_ptr cinfo) -{ +static boolean fill_input_buffer (j_decompress_ptr cinfo) { mem_src_ptr src = (mem_src_ptr) cinfo->src; size_t nbytes; - memcpy( src->buffer, src->inbuffer, (size_t) src->inbuffer_size ); + memcpy(src->buffer, src->inbuffer, (size_t) src->inbuffer_size); nbytes = src->inbuffer_size; if ( nbytes == 0 ) { @@ -297,10 +278,9 @@ static boolean fill_input_buffer (j_decompress_ptr cinfo) src->pub.bytes_in_buffer = nbytes; src->start_of_data = FALSE; - return( TRUE ); + return TRUE; } - /* * Skip data --- used to skip over a potentially large amount of * uninteresting data (such as an APPn marker). @@ -313,18 +293,15 @@ static boolean fill_input_buffer (j_decompress_ptr cinfo) * buffer is the application writer's problem. */ -static void skip_input_data (j_decompress_ptr cinfo, long num_bytes) -{ +static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) { mem_src_ptr src = (mem_src_ptr) cinfo->src; /* Just a dumb implementation for now. Could use fseek() except * it doesn't work on pipes. Not clear that being smart is worth * any trouble anyway --- large skips are infrequent. */ - if ( num_bytes > 0 ) - { - while ( num_bytes > (long) src->pub.bytes_in_buffer ) - { + if ( num_bytes > 0 ) { + while ( num_bytes > (long) src->pub.bytes_in_buffer ) { num_bytes -= (long) src->pub.bytes_in_buffer; (void) fill_input_buffer(cinfo); /* note we assume that fill_input_buffer will never return FALSE, @@ -336,7 +313,6 @@ static void skip_input_data (j_decompress_ptr cinfo, long num_bytes) } } - /* * Terminate source --- called by jpeg_finish_decompress * after all data has been read. Often a no-op. @@ -346,20 +322,17 @@ static void skip_input_data (j_decompress_ptr cinfo, long num_bytes) * for error exit. */ -static void term_source (j_decompress_ptr cinfo) -{ +static void term_source(j_decompress_ptr cinfo) { /* no work necessary here */ } - /* * Prepare for input from a memory stream. * The caller must have already opened the stream, and is responsible * for closing it after finishing decompression. */ -void zm_jpeg_mem_src( j_decompress_ptr cinfo, const JOCTET *inbuffer, int inbuffer_size ) -{ +void zm_jpeg_mem_src(j_decompress_ptr cinfo, const JOCTET *inbuffer, int inbuffer_size) { mem_src_ptr src; /* The source object and input buffer are made permanent so that a series @@ -369,19 +342,15 @@ void zm_jpeg_mem_src( j_decompress_ptr cinfo, const JOCTET *inbuffer, int inbuff * This makes it unsafe to use this manager and a different source * manager serially with the same JPEG object. Caveat programmer. */ - if ( cinfo->src == NULL ) - { + if ( cinfo->src == nullptr ) { /* first time for this JPEG object? */ cinfo->src = (struct jpeg_source_mgr *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, SIZEOF(mem_source_mgr)); src = (mem_src_ptr) cinfo->src; src->buffer = (JOCTET *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, inbuffer_size * SIZEOF(JOCTET)); src->inbuffer_size_hwm = inbuffer_size; - } - else - { + } else { src = (mem_src_ptr) cinfo->src; - if ( src->inbuffer_size_hwm < inbuffer_size ) - { + if ( src->inbuffer_size_hwm < inbuffer_size ) { src->buffer = (JOCTET *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, inbuffer_size * SIZEOF(JOCTET)); src->inbuffer_size_hwm = inbuffer_size; } @@ -396,10 +365,10 @@ void zm_jpeg_mem_src( j_decompress_ptr cinfo, const JOCTET *inbuffer, int inbuff src->inbuffer = (JOCTET *)inbuffer; src->inbuffer_size = inbuffer_size; src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ - src->pub.next_input_byte = NULL; /* until buffer loaded */ + src->pub.next_input_byte = nullptr; /* until buffer loaded */ } -void zm_use_std_huff_tables( j_decompress_ptr cinfo ) { +void zm_use_std_huff_tables(j_decompress_ptr cinfo) { /* JPEG standard Huffman tables (cf. JPEG standard section K.3) */ /* IMPORTANT: these are only valid for 8-bit data precision! */ static const JHUFF_TBL dclumin = { @@ -467,7 +436,6 @@ void zm_use_std_huff_tables( j_decompress_ptr cinfo ) { cinfo->dc_huff_tbl_ptrs[1] = (JHUFF_TBL*)&dcchrome; cinfo->ac_huff_tbl_ptrs[0] = (JHUFF_TBL*)&aclumin; cinfo->ac_huff_tbl_ptrs[1] = (JHUFF_TBL*)&acchrome; - } } diff --git a/src/zm_jpeg.h b/src/zm_jpeg.h index f0b4a355c..acf47f809 100644 --- a/src/zm_jpeg.h +++ b/src/zm_jpeg.h @@ -15,13 +15,12 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include +*/ +#include "jerror.h" #include "jinclude.h" #include "jpeglib.h" -#include "jerror.h" +#include // Stop complaints about deuplicate definitions #undef HAVE_STDLIB_H diff --git a/src/zm_libvlc_camera.cpp b/src/zm_libvlc_camera.cpp index 1998d1854..beda38498 100644 --- a/src/zm_libvlc_camera.cpp +++ b/src/zm_libvlc_camera.cpp @@ -15,14 +15,55 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ +*/ -#include "zm.h" -#include "zm_signal.h" #include "zm_libvlc_camera.h" -#if HAVE_LIBVLC +#include "zm_packet.h" +#include "zm_signal.h" +#include "zm_utils.h" +#include +#if HAVE_LIBVLC +static void *libvlc_lib = nullptr; +static void (*libvlc_media_player_release_f)(libvlc_media_player_t* ) = nullptr; +static void (*libvlc_media_release_f)(libvlc_media_t* ) = nullptr; +static void (*libvlc_release_f)(libvlc_instance_t* ) = nullptr; +static void (*libvlc_media_player_stop_f)(libvlc_media_player_t* ) = nullptr; +static libvlc_instance_t* (*libvlc_new_f)(int, const char* const *) = nullptr; +static void (*libvlc_log_set_f)(libvlc_instance_t*, libvlc_log_cb, void *) = nullptr; +static libvlc_media_t* (*libvlc_media_new_location_f)(libvlc_instance_t*, const char*) = nullptr; +static libvlc_media_player_t* (*libvlc_media_player_new_from_media_f)(libvlc_media_t*) = nullptr; +static void (*libvlc_video_set_format_f)(libvlc_media_player_t*, const char*, unsigned, unsigned, unsigned) = nullptr; +static void (*libvlc_video_set_callbacks_f)(libvlc_media_player_t*, libvlc_video_lock_cb, libvlc_video_unlock_cb, libvlc_video_display_cb, void*) = nullptr; +static int (*libvlc_media_player_play_f)(libvlc_media_player_t *) = nullptr; +static const char* (*libvlc_errmsg_f)(void) = nullptr; +static const char* (*libvlc_get_version_f)(void) = nullptr; + +void bind_libvlc_symbols() { + if(libvlc_lib != nullptr) // Safe-check + return; + + libvlc_lib = dlopen("libvlc.so", RTLD_LAZY | RTLD_GLOBAL); + if(!libvlc_lib){ + Error("Error loading libvlc: %s", dlerror()); + return; + } + + *(void**) (&libvlc_media_player_release_f) = dlsym(libvlc_lib, "libvlc_media_player_release"); + *(void**) (&libvlc_media_release_f) = dlsym(libvlc_lib, "libvlc_media_release"); + *(void**) (&libvlc_release_f) = dlsym(libvlc_lib, "libvlc_release"); + *(void**) (&libvlc_media_player_stop_f) = dlsym(libvlc_lib, "libvlc_media_player_stop"); + *(void**) (&libvlc_new_f) = dlsym(libvlc_lib, "libvlc_new"); + *(void**) (&libvlc_log_set_f) = dlsym(libvlc_lib, "libvlc_log_set"); + *(void**) (&libvlc_media_new_location_f) = dlsym(libvlc_lib, "libvlc_media_new_location"); + *(void**) (&libvlc_media_player_new_from_media_f) = dlsym(libvlc_lib, "libvlc_media_player_new_from_media"); + *(void**) (&libvlc_video_set_format_f) = dlsym(libvlc_lib, "libvlc_video_set_format"); + *(void**) (&libvlc_video_set_callbacks_f) = dlsym(libvlc_lib, "libvlc_video_set_callbacks"); + *(void**) (&libvlc_media_player_play_f) = dlsym(libvlc_lib, "libvlc_media_player_play"); + *(void**) (&libvlc_errmsg_f) = dlsym(libvlc_lib, "libvlc_errmsg"); + *(void**) (&libvlc_get_version_f) = dlsym(libvlc_lib, "libvlc_get_version"); +} // Do all the buffer checking work here to avoid unnecessary locking void* LibvlcLockBuffer(void* opaque, void** planes) { LibvlcPrivateData* data = reinterpret_cast(opaque); @@ -33,7 +74,7 @@ void* LibvlcLockBuffer(void* opaque, void** planes) { data->prevBuffer = buffer; *planes = data->buffer; - return NULL; + return nullptr; } void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes) { @@ -53,12 +94,16 @@ void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes) { // Return frames slightly faster than 1fps (if time() supports greater than one second resolution) if ( newFrame || difftime(now, data->prevTime) >= 0.8 ) { data->prevTime = now; - data->newImage.updateValueSignal(true); + { + std::lock_guard lck(data->newImageMutex); + data->newImage = true; + } + data->newImageCv.notify_all(); } } LibvlcCamera::LibvlcCamera( - int p_id, + const Monitor *monitor, const std::string &p_path, const std::string &p_method, const std::string &p_options, @@ -73,7 +118,7 @@ LibvlcCamera::LibvlcCamera( bool p_record_audio ) : Camera( - p_id, + monitor, LIBVLC_SRC, p_width, p_height, @@ -90,12 +135,12 @@ LibvlcCamera::LibvlcCamera( mMethod(p_method), mOptions(p_options) { - mLibvlcInstance = NULL; - mLibvlcMedia = NULL; - mLibvlcMediaPlayer = NULL; - mLibvlcData.buffer = NULL; - mLibvlcData.prevBuffer = NULL; - mOptArgV = NULL; + mLibvlcInstance = nullptr; + mLibvlcMedia = nullptr; + mLibvlcMediaPlayer = nullptr; + mLibvlcData.buffer = nullptr; + mLibvlcData.prevBuffer = nullptr; + mOptArgV = nullptr; /* 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 ) { @@ -124,42 +169,51 @@ LibvlcCamera::~LibvlcCamera() { if ( capture ) { Terminate(); } - if ( mLibvlcMediaPlayer != NULL ) { - libvlc_media_player_release(mLibvlcMediaPlayer); - mLibvlcMediaPlayer = NULL; + + mLibvlcData.newImageCv.notify_all(); // to unblock on termination (zm_terminate) + + if ( mLibvlcMediaPlayer != nullptr ) { + (*libvlc_media_player_release_f)(mLibvlcMediaPlayer); + mLibvlcMediaPlayer = nullptr; } - if ( mLibvlcMedia != NULL ) { - libvlc_media_release(mLibvlcMedia); - mLibvlcMedia = NULL; + if ( mLibvlcMedia != nullptr ) { + (*libvlc_media_release_f)(mLibvlcMedia); + mLibvlcMedia = nullptr; } - if ( mLibvlcInstance != NULL ) { - libvlc_release(mLibvlcInstance); - mLibvlcInstance = NULL; + if ( mLibvlcInstance != nullptr ) { + (*libvlc_release_f)(mLibvlcInstance); + mLibvlcInstance = nullptr; } - if ( mOptArgV != NULL ) { + if (libvlc_lib) { + dlclose(libvlc_lib); + libvlc_lib = nullptr; + } + if ( mOptArgV != nullptr ) { delete[] mOptArgV; } } void LibvlcCamera::Initialise() { + bind_libvlc_symbols(); } void LibvlcCamera::Terminate() { - libvlc_media_player_stop(mLibvlcMediaPlayer); + (*libvlc_media_player_stop_f)(mLibvlcMediaPlayer); if ( mLibvlcData.buffer ) { zm_freealigned(mLibvlcData.buffer); - mLibvlcData.buffer = NULL; + mLibvlcData.buffer = nullptr; } + if ( mLibvlcData.prevBuffer ) { zm_freealigned(mLibvlcData.prevBuffer); - mLibvlcData.prevBuffer = NULL; + mLibvlcData.prevBuffer = nullptr; } } int LibvlcCamera::PrimeCapture() { - Info("Priming capture from %s", mPath.c_str()); + 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" ) @@ -173,45 +227,45 @@ 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()); } } - mLibvlcInstance = libvlc_new(opVect.size(), (const char* const*)mOptArgV); - if ( mLibvlcInstance == NULL ) { - Error("Unable to create libvlc instance due to: %s", libvlc_errmsg()); + mLibvlcInstance = (*libvlc_new_f)(opVect.size(), (const char* const*)mOptArgV); + if ( mLibvlcInstance == nullptr ) { + Error("Unable to create libvlc instance due to: %s", (*libvlc_errmsg_f)()); return -1; } - libvlc_log_set(mLibvlcInstance, LibvlcCamera::log_callback, NULL); + (*libvlc_log_set_f)(mLibvlcInstance, LibvlcCamera::log_callback, nullptr); - mLibvlcMedia = libvlc_media_new_location(mLibvlcInstance, mPath.c_str()); - if ( mLibvlcMedia == NULL ) { - Error("Unable to open input %s due to: %s", mPath.c_str(), libvlc_errmsg()); + mLibvlcMedia = (*libvlc_media_new_location_f)(mLibvlcInstance, mPath.c_str()); + if ( mLibvlcMedia == nullptr ) { + Error("Unable to open input %s due to: %s", mPath.c_str(), (*libvlc_errmsg_f)()); return -1; } - mLibvlcMediaPlayer = libvlc_media_player_new_from_media(mLibvlcMedia); - if ( mLibvlcMediaPlayer == NULL ) { - Error("Unable to create player for %s due to: %s", mPath.c_str(), libvlc_errmsg()); + mLibvlcMediaPlayer = (*libvlc_media_player_new_from_media_f)(mLibvlcMedia); + if ( mLibvlcMediaPlayer == nullptr ) { + Error("Unable to create player for %s due to: %s", mPath.c_str(), (*libvlc_errmsg_f)()); return -1; } - libvlc_video_set_format(mLibvlcMediaPlayer, mTargetChroma.c_str(), width, height, width * mBpp); - libvlc_video_set_callbacks(mLibvlcMediaPlayer, &LibvlcLockBuffer, &LibvlcUnlockBuffer, NULL, &mLibvlcData); + (*libvlc_video_set_format_f)(mLibvlcMediaPlayer, mTargetChroma.c_str(), width, height, width * mBpp); + (*libvlc_video_set_callbacks_f)(mLibvlcMediaPlayer, &LibvlcLockBuffer, &LibvlcUnlockBuffer, nullptr, &mLibvlcData); mLibvlcData.bufferSize = width * height * mBpp; // 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); - libvlc_media_player_play(mLibvlcMediaPlayer); + mLibvlcData.newImage = false; + + (*libvlc_media_player_play_f)(mLibvlcMediaPlayer); return 0; } @@ -222,27 +276,26 @@ int LibvlcCamera::PreCapture() { } // Should not return -1 as cancels capture. Always wait for image if available. -int LibvlcCamera::Capture(Image &image) { - +int LibvlcCamera::Capture(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(); - image.Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp); - mLibvlcData.newImage.setValueImmediate(false); + zm_packet->image->Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp); + zm_packet->packet.stream_index = mVideoStreamId; + zm_packet->stream = mVideoStream; mLibvlcData.mutex.unlock(); return 1; } -int LibvlcCamera::CaptureAndRecord(Image &image, timeval recording, char* event_directory) { - return 0; -} - int LibvlcCamera::PostCapture() { return 0; } @@ -266,9 +319,9 @@ void LibvlcCamera::log_callback(void *ptr, int level, const libvlc_log_t *ctx, c } if ( log ) { - char logString[8192]; - vsnprintf(logString, sizeof(logString)-1, fmt, vargs); - log->logPrint(false, __FILE__, __LINE__, log_level, logString); + char logString[8192]; + vsnprintf(logString, sizeof(logString) - 1, fmt, vargs); + log->logPrint(false, __FILE__, __LINE__, log_level, "%s", logString); } } #endif // HAVE_LIBVLC diff --git a/src/zm_libvlc_camera.h b/src/zm_libvlc_camera.h index 038d30f05..c516a63e0 100644 --- a/src/zm_libvlc_camera.h +++ b/src/zm_libvlc_camera.h @@ -20,9 +20,9 @@ #ifndef ZM_LIBVLC_CAMERA_H #define ZM_LIBVLC_CAMERA_H -#include "zm_buffer.h" #include "zm_camera.h" -#include "zm_thread.h" +#include +#include #if HAVE_LIBVLC @@ -31,14 +31,16 @@ #endif // Used by libvlc callbacks -struct LibvlcPrivateData -{ +struct LibvlcPrivateData { uint8_t* buffer; uint8_t* prevBuffer; time_t prevTime; uint32_t bufferSize; - Mutex mutex; - ThreadData newImage; + std::mutex mutex; + + bool newImage; + std::mutex newImageMutex; + std::condition_variable newImageCv; }; class LibvlcCamera : public Camera { @@ -58,7 +60,7 @@ protected: libvlc_media_player_t *mLibvlcMediaPlayer; public: - LibvlcCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); + LibvlcCamera( const Monitor *monitor, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); ~LibvlcCamera(); const std::string &Path() const { return mPath; } @@ -68,12 +70,11 @@ public: void Initialise(); void Terminate(); - int PrimeCapture(); - int PreCapture(); - int Capture( Image &image ); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ); - int PostCapture(); - int Close() { return 0; }; + int PrimeCapture() override; + int PreCapture() override; + int Capture(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 new file mode 100644 index 000000000..3ff7804b6 --- /dev/null +++ b/src/zm_libvnc_camera.cpp @@ -0,0 +1,259 @@ +#include "zm_libvnc_camera.h" + +#include "zm_packet.h" +#include + +#if HAVE_LIBVNC + +static int TAG_0; +static int TAG_1; +static int TAG_2; +static void *libvnc_lib = nullptr; +static void *(*rfbClientGetClientData_f)(rfbClient*, void*) = nullptr; +static rfbClient *(*rfbGetClient_f)(int, int, int) = nullptr; +static void (*rfbClientSetClientData_f)(rfbClient*, void*, void*) = nullptr; +static rfbBool (*rfbInitClient_f)(rfbClient*, int*, char**) = nullptr; +static void (*rfbClientCleanup_f)(rfbClient*) = nullptr; +static int (*WaitForMessage_f)(rfbClient*, unsigned int) = nullptr; +static rfbBool (*HandleRFBServerMessage_f)(rfbClient*) = nullptr; + +void bind_libvnc_symbols() { + if (libvnc_lib != nullptr) // Safe-check + return; + + libvnc_lib = dlopen("libvncclient.so", RTLD_LAZY | RTLD_GLOBAL); + if (!libvnc_lib) { + Error("Error loading libvncclient.so: %s", dlerror()); + return; + } + + *(void**) (&rfbClientGetClientData_f) = dlsym(libvnc_lib, "rfbClientGetClientData"); + *(void**) (&rfbGetClient_f) = dlsym(libvnc_lib, "rfbGetClient"); + *(void**) (&rfbClientSetClientData_f) = dlsym(libvnc_lib, "rfbClientSetClientData"); + *(void**) (&rfbInitClient_f) = dlsym(libvnc_lib, "rfbInitClient"); + *(void**) (&rfbClientCleanup_f) = dlsym(libvnc_lib, "rfbClientCleanup"); + *(void**) (&WaitForMessage_f) = dlsym(libvnc_lib, "WaitForMessage"); + *(void**) (&HandleRFBServerMessage_f) = dlsym(libvnc_lib, "HandleRFBServerMessage"); +} + +static void GotFrameBufferUpdateCallback(rfbClient *rfb, int x, int y, int w, int h) { + VncPrivateData *data = (VncPrivateData *)(*rfbClientGetClientData_f)(rfb, &TAG_0); + data->buffer = rfb->frameBuffer; + Debug(1, "GotFrameBufferUpdateallback x:%d y:%d w%d h:%d width: %d, height: %d, buffer %p", + x,y,w,h, rfb->width, rfb->height, rfb->frameBuffer); +} + +static char* GetPasswordCallback(rfbClient* cl) { + 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){ + if (credentialType != rfbCredentialTypeUser) { + Debug(1, "credentialType != rfbCredentialTypeUser"); + return nullptr; + } + rfbCredential *c = (rfbCredential *)malloc(sizeof(rfbCredential)); + + Debug(1, "Getcredentials: %s:%s", + static_cast((*rfbClientGetClientData_f)(cl, &TAG_1)), + static_cast((*rfbClientGetClientData_f)(cl, &TAG_2))); + c->userCredential.password = strdup((const char *)(*rfbClientGetClientData_f)(cl, &TAG_1)); + c->userCredential.username = strdup((const char *)(*rfbClientGetClientData_f)(cl, &TAG_2)); + return c; +} + +static rfbBool resize(rfbClient* client) { + if (client->frameBuffer) { + Debug(1, "Freeing old frame buffer"); + av_free(client->frameBuffer); + } + + int bufferSize = 4*client->width*client->height; + // libVNC doesn't do alignment or padding in each line + //SWScale::GetBufferSize(AV_PIX_FMT_RGBA, client->width, client->height); + client->frameBuffer = (uint8_t *)av_malloc(bufferSize); + Debug(1, "Allocing new frame buffer %dx%d = %d", client->width, client->height, bufferSize); + + return TRUE; +} + +VncCamera::VncCamera( + const Monitor *monitor, + const std::string &host, + const std::string &port, + const std::string &user, + const std::string &pass, + 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 ) : + Camera( + monitor, + VNC_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 + ), + mRfb(nullptr), + mVncData({}), + mHost(host), + mPort(port), + mUser(user), + mPass(pass) +{ + if (colours == ZM_COLOUR_RGB32) { + subpixelorder = ZM_SUBPIX_ORDER_RGBA; + mImgPixFmt = AV_PIX_FMT_RGBA; + } else if (colours == ZM_COLOUR_RGB24) { + subpixelorder = ZM_SUBPIX_ORDER_RGB; + mImgPixFmt = AV_PIX_FMT_RGB24; + } else if (colours == ZM_COLOUR_GRAY8) { + subpixelorder = ZM_SUBPIX_ORDER_NONE; + mImgPixFmt = AV_PIX_FMT_GRAY8; + } else { + Panic("Unexpected colours: %d", colours); + } + + if (capture) { + Debug(3, "Initializing Client"); + bind_libvnc_symbols(); + scale.init(); + } +} + +VncCamera::~VncCamera() { + if (libvnc_lib) { + dlclose(libvnc_lib); + libvnc_lib = nullptr; + } +} + +int VncCamera::PrimeCapture() { + if (libvnc_lib == nullptr) { + Error("No libvnc shared lib bound."); + return -1; + } + Debug(1, "Priming capture from %s", mHost.c_str()); + + if (!mRfb) { + mVncData.buffer = nullptr; + mVncData.width = 0; + mVncData.height = 0; + + // TODO, support 8bit or 24bit + mRfb = (*rfbGetClient_f)(8 /* bits per sample */, 3 /* samples per pixel */, 4 /* bytes Per Pixel */); + mRfb->MallocFrameBuffer = resize; + + (*rfbClientSetClientData_f)(mRfb, &TAG_0, &mVncData); + (*rfbClientSetClientData_f)(mRfb, &TAG_1, (void *)mPass.c_str()); + (*rfbClientSetClientData_f)(mRfb, &TAG_2, (void *)mUser.c_str()); + + mRfb->GotFrameBufferUpdate = GotFrameBufferUpdateCallback; + mRfb->GetPassword = GetPasswordCallback; + mRfb->GetCredential = GetCredentialsCallback; + + mRfb->programName = "Zoneminder VNC Monitor"; + if (mRfb->serverHost) free(mRfb->serverHost); + mRfb->serverHost = strdup(mHost.c_str()); + mRfb->serverPort = atoi(mPort.c_str()); + if (!mRfb->serverPort) { + Debug(1, "Defaulting to port 5900"); + mRfb->serverPort = 5900; + } + + } else { + Debug(1, "mRfb already exists?"); + } + if (!(*rfbInitClient_f)(mRfb, 0, nullptr)) { + /* IF rfbInitClient fails, it calls rdbClientCleanup which will free mRfb */ + mRfb = nullptr; + return -1; + } + if (((unsigned int)mRfb->width != width) or ((unsigned int)mRfb->height != height)) { + Warning("Specified dimensions do not match screen size monitor: (%dx%d) != vnc: (%dx%d)", + width, height, mRfb->width, mRfb->height); + } + getVideoStream(); + + return 1; +} + +int VncCamera::PreCapture() { + int rc = (*WaitForMessage_f)(mRfb, 500); + if (rc < 0) { + return -1; + } else if (!rc) { + return rc; + } + rfbBool res = (*HandleRFBServerMessage_f)(mRfb); + Debug(3, "PreCapture rc from HandleMessage %d", res == TRUE ? 1 : -1); + return res == TRUE ? 1 : -1; +} + +int VncCamera::Capture(std::shared_ptr &zm_packet) { + if (!mVncData.buffer) { + Debug(1, "No buffer"); + return 0; + } + if (!zm_packet->image) { + Debug(1, "Allocating image %dx%d %dcolours = %d", width, height, colours, colours*pixels); + zm_packet->image = new Image(width, height, colours, subpixelorder); + } + zm_packet->keyframe = 1; + zm_packet->codec_type = AVMEDIA_TYPE_VIDEO; + zm_packet->packet.stream_index = mVideoStreamId; + zm_packet->stream = mVideoStream; + + uint8_t *directbuffer = zm_packet->image->WriteBuffer(width, height, colours, subpixelorder); + Debug(1, "scale src %p, %d, dest %p %d %d %dx%d %dx%d", mVncData.buffer, + mRfb->si.framebufferWidth * mRfb->si.framebufferHeight * 4, + directbuffer, + width * height * colours, + mImgPixFmt, + mRfb->si.framebufferWidth, + mRfb->si.framebufferHeight, + width, + height); + + int rc = scale.Convert( + mVncData.buffer, + mRfb->si.framebufferWidth * mRfb->si.framebufferHeight * 4, + //SWScale::GetBufferSize(AV_PIX_FMT_RGBA, mRfb->si.framebufferWidth, mRfb->si.framebufferHeight), + directbuffer, + width * height * colours, + AV_PIX_FMT_RGBA, + mImgPixFmt, + mRfb->si.framebufferWidth, + mRfb->si.framebufferHeight, + width, + height); + return rc == 0 ? 1 : rc; +} + +int VncCamera::PostCapture() { + return 1; +} + +int VncCamera::Close() { + if (capture and mRfb) { + if (mRfb->frameBuffer) + free(mRfb->frameBuffer); + (*rfbClientCleanup_f)(mRfb); + mRfb = nullptr; + } + return 1; +} +#endif diff --git a/src/zm_libvnc_camera.h b/src/zm_libvnc_camera.h new file mode 100644 index 000000000..59c531e83 --- /dev/null +++ b/src/zm_libvnc_camera.h @@ -0,0 +1,63 @@ + +#ifndef ZN_LIBVNC_CAMERA_H +#define ZN_LIBVNC_CAMERA_H + +#include "zm_camera.h" +#include "zm_swscale.h" + +#if HAVE_LIBVNC +#include + +// Older versions of libvncserver defined a max macro in rfb/rfbproto.h +// Undef it here so it doesn't collide with std::max +// TODO: Remove this once CentOS 7 support is dropped +#ifdef max +#undef max +#endif + + +// Used by vnc callbacks +struct VncPrivateData { + uint8_t *buffer; + uint8_t width; + uint8_t height; +}; + +class VncCamera : public Camera { +protected: + rfbClient *mRfb; + VncPrivateData mVncData; + SWScale scale; + AVPixelFormat mImgPixFmt; + std::string mHost; + std::string mPort; + std::string mUser; + std::string mPass; +public: + VncCamera( + const Monitor *monitor, + const std::string &host, + const std::string &port, + const std::string &user, + const std::string &pass, + 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); + + ~VncCamera(); + + int PreCapture() override; + int PrimeCapture() override; + int Capture(std::shared_ptr &packet) override; + int PostCapture() override; + int Close() override; +}; + +#endif // HAVE_LIBVNC +#endif // ZN_LIBVNC_CAMERA_H 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 c6030f08a..68465fa21 100644 --- a/src/zm_local_camera.cpp +++ b/src/zm_local_camera.cpp @@ -17,20 +17,16 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" - -#if ZM_HAS_V4L - #include "zm_local_camera.h" -#include -#include +#include "zm_packet.h" +#include "zm_utils.h" #include -#include #include -#include -#include -#include +#include +#include + +#if ZM_HAS_V4L2 /* Workaround for GNU/kFreeBSD and FreeBSD */ #if defined(__FreeBSD_kernel__) || defined(__FreeBSD__) @@ -40,6 +36,7 @@ #endif static unsigned int BigEndian; +static bool primed; static int vidioctl(int fd, int request, void *arg) { int result = -1; @@ -49,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[] = { @@ -283,8 +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; @@ -294,21 +217,14 @@ 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 = 0; -#endif // HAVE_LIBSWSCALE +AVFrame **LocalCamera::capturePictures = nullptr; -LocalCamera *LocalCamera::last_camera = NULL; +LocalCamera *LocalCamera::last_camera = nullptr; LocalCamera::LocalCamera( - int p_id, + const Monitor *monitor, const std::string &p_device, int p_channel, int p_standard, @@ -324,15 +240,15 @@ LocalCamera::LocalCamera( int p_hue, int p_colour, bool p_capture, - bool p_record_audio, + bool p_record_audio, unsigned int p_extras) : - Camera( p_id, LOCAL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), - device( p_device ), - channel( p_channel ), - standard( p_standard ), - palette( p_palette ), - channel_index( 0 ), - extras ( p_extras ) + Camera(monitor, LOCAL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio), + device(p_device), + channel(p_channel), + standard(p_standard), + palette(p_palette), + channel_index(0), + extras(p_extras) { // If we are the first, or only, input on this device then // do the initial opening etc @@ -341,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 ) { - Debug( 2, "V4L support enabled, using V4L%d api", v4l_version ); + 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++; @@ -360,776 +276,458 @@ 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 ) { + Debug(2, "little-endian processor detected"); + } else if (*(unsigned char*)&checkval == 0xAA) { BigEndian = 1; - Debug(2,"Big-endian processor detected"); + Debug(2, "Big-endian processor detected"); } else { Error("Unable to detect the processor's endianness. Assuming little-endian."); BigEndian = 0; } -#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 (capture) { + if (last_camera) { + if (standard != last_camera->standard) + Warning("Different video standards defined for monitors sharing same device, results may be unpredictable or completely wrong"); - if ( standard != last_camera->standard ) - Warning( "Different video standards defined for monitors sharing same device, results may be unpredictable or completely wrong" ); + if (palette != last_camera->palette) + Warning("Different video palettes defined for monitors sharing same device, results may be unpredictable or completely wrong"); - if ( palette != last_camera->palette ) - Warning( "Different video palettes defined for monitors sharing same device, results may be unpredictable or completely wrong" ); - - if ( width != last_camera->width || height != last_camera->height ) - Warning( "Different capture sizes defined for monitors sharing same device, results may be unpredictable or completely wrong" ); + if (width != last_camera->width or height != last_camera->height) + Warning("Different capture sizes defined for monitors sharing same device, results may be unpredictable or completely wrong"); } -#if HAVE_LIBSWSCALE /* 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; + /* 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) { + Info( + "No direct match for the selected palette (%d) and target colorspace (%02u). Format conversion is required, performance penalty expected", + capturePixFormat, + colours); + } + /* 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; - - /* BGR24 palette and 24bit target colourspace */ - } else if ( palette == V4L2_PIX_FMT_BGR24 && colours == ZM_COLOUR_RGB24 ) { - conversion_type = 0; - subpixelorder = ZM_SUBPIX_ORDER_BGR; - - /* Grayscale palette and grayscale target colourspace */ - } else if ( palette == V4L2_PIX_FMT_GREY && colours == ZM_COLOUR_GRAY8 ) { - conversion_type = 0; + imagePixFormat = AV_PIX_FMT_RGB24; + } else if (colours == ZM_COLOUR_GRAY8) { 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 */ + imagePixFormat = AV_PIX_FMT_GRAY8; } 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 + 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 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 && sseversion >= 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 */ - } - } -#else - /* Don't have swscale, see what we can do */ - conversion_type = 2; -#endif - /* Our YUYV->Grayscale conversion is a lot faster than swscale's */ - if ( colours == ZM_COLOUR_GRAY8 && (palette == VIDEO_PALETTE_YUYV || palette == VIDEO_PALETTE_YUV422) ) { - conversion_type = 2; - } - - 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 && sseversion >= 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."); - } + if (!sws_isSupportedOutput(imagePixFormat)) { + Error("swscale does not support the target format: 0x%d", imagePixFormat); + conversion_type = 2; /* Try ZM format conversions */ } } - } -#endif // ZM_HAS_V4L1 + /* 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); + Debug(3, "Selected subpixelorder: %u", subpixelorder); -#if HAVE_LIBSWSCALE /* Initialize swscale stuff */ - if ( capture && 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(width, height, capturePixFormat, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL); + imgConversionContext = sws_getContext( + width, height, capturePixFormat, + width, height, imagePixFormat, SWS_BICUBIC, + nullptr, nullptr, nullptr); - if ( !imgConversionContext ) { + if (!imgConversionContext) { Fatal("Unable to initialise image scaling context"); } } else { - tmpPicture = NULL; - imgConversionContext = NULL; - } -#endif + tmpPicture = nullptr; + imgConversionContext = nullptr; + } // end if capture and conversion_tye == swscale + 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 = NULL; + imgConversionContext = nullptr; av_frame_free(&tmpPicture); } -#endif -} +} // end LocalCamera::~LocalCamera + +int LocalCamera::Close() { + if (device_prime && capture) + Terminate(); + return 0; +}; void LocalCamera::Initialise() { -#if HAVE_LIBSWSCALE - if ( logDebugging() ) - av_log_set_level(AV_LOG_DEBUG); - else - av_log_set_level(AV_LOG_QUIET); -#endif // HAVE_LIBSWSCALE - Debug(3, "Opening video device %s", device.c_str()); - //if ( (vid_fd = open( device.c_str(), O_RDWR|O_NONBLOCK, 0 )) < 0 ) - if ( (vid_fd = open(device.c_str(), O_RDWR, 0)) < 0 ) + 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 = %08x\n" - " v4l2_data.fmt.fmt.pix.height = %08x\n" - " v4l2_data.fmt.fmt.pix.pixelformat = %08x\n" - " v4l2_data.fmt.fmt.pix.field = %08x\n" - " v4l2_data.fmt.fmt.pix.bytesperline = %08x\n" - " v4l2_data.fmt.fmt.pix.sizeimage = %08x\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 ) { + 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)); + } + } - /* Note VIDIOC_S_FMT may change width and height. */ - Debug(4, - " v4l2_data.fmt.type = %08x\n" - " v4l2_data.fmt.fmt.pix.width = %08x\n" - " v4l2_data.fmt.fmt.pix.height = %08x\n" - " v4l2_data.fmt.fmt.pix.pixelformat = %08x\n" - " v4l2_data.fmt.fmt.pix.field = %08x\n" - " v4l2_data.fmt.fmt.pix.bytesperline = %08x\n" - " v4l2_data.fmt.fmt.pix.sizeimage = %08x\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 - ); + /* 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 + ); - /* 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 (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"); + } - 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)); - } + /* 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",jpeg_comp.quality); - Debug(4, "JPEG markers: %#x",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(NULL, 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) ) { - Fatal("Device does not support video standard %d", standard); - } - - stdId = standard; - if ( (input.std != V4L2_STD_UNKNOWN) && (vidioctl(vid_fd, VIDIOC_S_STD, &stdId) < 0) ) { - Fatal("Failed to set video standard %d: %d %s", standard, errno, strerror(errno)); - } - - 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; @@ -1140,48 +738,20 @@ 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; - } -#endif // ZM_HAS_V4L1 close(vid_fd); -} // end Terminate + primed = false; +} // end 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; @@ -1209,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)); @@ -1245,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); } } } @@ -1266,64 +846,74 @@ 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) #define capString(test,prefix,yesString,noString,capability) \ (test) ? (prefix yesString " " capability "\n") : (prefix noString " " capability "\n") -bool LocalCamera::GetCurrentSettings( const char *device, char *output, int version, bool verbose ) { +bool LocalCamera::GetCurrentSettings( + const 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); - else - sprintf(queryDevice, "/dev/video%d", devIndex); + if (!device.empty()) { + queryDevice = device; + } else { + 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 ) - sprintf(output+strlen(output), "Error, failed to open video device %s: %s\n", - queryDevice, strerror(errno)); - else - sprintf(output+strlen(output), "error%d\n", errno); + 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.c_str(), strerror(errno)); + } else { + output_ptr += sprintf(output_ptr, "error%d\n", errno); + } return false; } else { return true; } } - if ( verbose ) - sprintf(output+strlen(output), "Video Device: %s\n", queryDevice); - else - sprintf(output+strlen(output), "d:%s|", queryDevice); -#if ZM_HAS_V4L2 - if ( version == 2 ) { - struct v4l2_capability vid_cap; - if ( vidioctl(vid_fd, VIDIOC_QUERYCAP, &vid_cap) < 0 ) { + if (verbose) { + output_ptr += sprintf(output_ptr, "Video Device: %s\n", queryDevice.c_str()); + } else { + output_ptr += sprintf(output_ptr, "d:%s|", queryDevice.c_str()); + } + + 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 ) - sprintf(output, "Error, failed to query video capabilities %s: %s\n", - queryDevice, strerror(errno)); - else - sprintf(output, "error%d\n", errno); - return false; + if (verbose) { + output_ptr += sprintf(output_ptr, "Error, failed to query video capabilities %s: %s\n", + queryDevice.c_str(), strerror(errno)); + } else { + output_ptr += sprintf(output_ptr, "error%d\n", errno); + } + if (!device.empty()) { + return false; + } } if ( verbose ) { - sprintf(output+strlen(output), "General Capabilities\n"); - sprintf(output+strlen(output), " Driver: %s\n", vid_cap.driver); - sprintf(output+strlen(output), " Card: %s\n", vid_cap.card); - sprintf(output+strlen(output), " Bus: %s\n", vid_cap.bus_info); - sprintf(output+strlen(output), " Version: %u.%u.%u\n", - (vid_cap.version>>16)&0xff, (vid_cap.version>>8)&0xff, vid_cap.version&0xff); - sprintf(output+strlen(output), " Type: 0x%x\n%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + output_ptr += sprintf(output_ptr, "General Capabilities\n" + " Driver: %s\n" + " Card: %s\n" + " Bus: %s\n" + " Version: %u.%u.%u\n" + " Type: 0x%x\n%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + vid_cap.driver, vid_cap.card, vid_cap.bus_info, + (vid_cap.version>>16)&0xff, (vid_cap.version>>8)&0xff, vid_cap.version&0xff, vid_cap.capabilities, capString(vid_cap.capabilities&V4L2_CAP_VIDEO_CAPTURE, " ", "Supports", "Does not support", "video capture (X)"), capString(vid_cap.capabilities&V4L2_CAP_VIDEO_OUTPUT, " ", "Supports", "Does not support", "video output"), @@ -1345,18 +935,17 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers capString(vid_cap.capabilities&V4L2_CAP_STREAMING, " ", "Supports", "Does not support", "streaming i/o (X)") ); } else { - sprintf(output+strlen(output), "D:%s|", vid_cap.driver); - sprintf(output+strlen(output), "C:%s|", vid_cap.card); - sprintf(output+strlen(output), "B:%s|", vid_cap.bus_info); - sprintf(output+strlen(output), "V:%u.%u.%u|", (vid_cap.version>>16)&0xff, (vid_cap.version>>8)&0xff, vid_cap.version&0xff); - sprintf(output+strlen(output), "T:0x%x|", vid_cap.capabilities); + output_ptr += sprintf(output_ptr, "D:%s|C:%s|B:%s|V:%u.%u.%u|T:0x%x|" + , vid_cap.driver + , vid_cap.card + , vid_cap.bus_info + , (vid_cap.version>>16)&0xff, (vid_cap.version>>8)&0xff, vid_cap.version&0xff + , vid_cap.capabilities); } - if ( verbose ) - sprintf(output+strlen(output), " Standards:\n"); - else - sprintf(output+strlen(output), "S:"); - struct v4l2_standard standard; + output_ptr += sprintf(output_ptr, verbose ? " Standards:\n" : "S:"); + + v4l2_standard standard = {}; int standardIndex = 0; do { memset(&standard, 0, sizeof(standard)); @@ -1370,29 +959,24 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers } else { Error("Failed to enumerate standard %d: %d %s", standard.index, errno, strerror(errno)); if ( verbose ) - sprintf(output, "Error, failed to enumerate standard %d: %d %s\n", standard.index, errno, strerror(errno)); + output_ptr += sprintf(output_ptr, "Error, failed to enumerate standard %d: %d %s\n", standard.index, errno, strerror(errno)); else - sprintf(output, "error%d\n", errno); + output_ptr += sprintf(output_ptr, "error%d\n", errno); + // Why return? Why not continue trying other things? return false; } } - if ( verbose ) - sprintf(output+strlen(output), " %s\n", standard.name); - else - sprintf(output+strlen(output), "%s/", standard.name); - } - while ( standardIndex++ >= 0 ); - if ( !verbose && output[strlen(output)-1] == '/') - output[strlen(output)-1] = '|'; + output_ptr += sprintf(output_ptr, (verbose ? " %s\n" : "%s/"), standard.name); + } while ( standardIndex++ >= 0 ); + + if ( !verbose && (*(output_ptr-1) == '/') ) + *(output_ptr-1) = '|'; + + output_ptr += sprintf(output_ptr, verbose ? " Formats:\n" : "F:"); - if ( verbose ) - sprintf(output+strlen(output), " Formats:\n"); - else - sprintf(output+strlen(output), "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; @@ -1403,53 +987,54 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers } else { Error("Failed to enumerate format %d: %s", format.index, strerror(errno)); if ( verbose ) - sprintf(output, "Error, failed to enumerate format %d: %s\n", format.index, strerror(errno)); + output_ptr += sprintf(output_ptr, "Error, failed to enumerate format %d: %s\n", format.index, strerror(errno)); else - sprintf(output, "error%d\n", errno); + output_ptr += sprintf(output_ptr, "error%d\n", errno); return false; } } if ( verbose ) - sprintf( - output+strlen(output), - " %s (0x%02hhx%02hhx%02hhx%02hhx)\n", + output_ptr += sprintf( + output_ptr, + " %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 - sprintf( - output+strlen(output), - "0x%02hhx%02hhx%02hhx%02hhx/", - (format.pixelformat>>24)&0xff, - (format.pixelformat>>16)&0xff, - (format.pixelformat>>8)&0xff, - (format.pixelformat)&0xff); + output_ptr += sprintf( + output_ptr, + "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 ) - output[strlen(output)-1] = '|'; - else - sprintf(output+strlen(output), "Crop Capabilities\n"); - struct v4l2_cropcap cropcap; - memset(&cropcap, 0, sizeof(cropcap)); + if ( !verbose ) + *(output_ptr-1) = '|'; + else + output_ptr += sprintf(output_ptr, "Crop Capabilities\n"); + + v4l2_cropcap cropcap = {}; cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if ( vidioctl( vid_fd, VIDIOC_CROPCAP, &cropcap ) < 0 ) { + + 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 */ Error("Failed to query crop capabilities: %s", strerror(errno)); } if ( verbose ) { - sprintf(output+strlen(output), " Cropping is not supported\n"); + output_ptr += sprintf(output_ptr, " Cropping is not supported\n"); } else { /* Send fake crop bounds to not confuse things parsing this, such as monitor probe */ - sprintf(output+strlen(output), "B:%dx%d|",0,0); + 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 ) { @@ -1459,19 +1044,23 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers } if ( verbose ) { - sprintf(output+strlen(output), " Cropping is not supported\n"); + output_ptr += sprintf(output_ptr, " Cropping is not supported\n"); } else { /* Send fake crop bounds to not confuse things parsing this, such as monitor probe */ - sprintf(output+strlen(output), "B:%dx%d|",0,0); + output_ptr += sprintf(output_ptr, "B:%dx%d|",0,0); } } else { /* Cropping supported */ if ( verbose ) { - sprintf(output+strlen(output), " Bounds: %d x %d\n", cropcap.bounds.width, cropcap.bounds.height); - sprintf(output+strlen(output), " Default: %d x %d\n", cropcap.defrect.width, cropcap.defrect.height); - sprintf(output+strlen(output), " Current: %d x %d\n", crop.c.width, crop.c.height); + output_ptr += sprintf(output_ptr, + " Bounds: %d x %d\n" + " Default: %d x %d\n" + " Current: %d x %d\n" + , cropcap.bounds.width, cropcap.bounds.height + , cropcap.defrect.width, cropcap.defrect.height + , crop.c.width, crop.c.height); } else { - sprintf(output+strlen(output), "B:%dx%d|", cropcap.bounds.width, cropcap.bounds.height); + output_ptr += sprintf(output_ptr, "B:%dx%d|", cropcap.bounds.width, cropcap.bounds.height); } } } /* Crop code */ @@ -1488,17 +1077,14 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers } Error("Failed to enumerate input %d: %s", input.index, strerror(errno)); if ( verbose ) - sprintf(output, "Error, failed to enumerate input %d: %s\n", input.index, strerror(errno)); + output_ptr += sprintf(output_ptr, "Error, failed to enumerate input %d: %s\n", input.index, strerror(errno)); else - sprintf(output, "error%d\n", errno); + output_ptr += sprintf(output_ptr, "error%d\n", errno); return false; } } while ( inputIndex++ >= 0 ); - if ( verbose ) - sprintf(output+strlen(output), "Inputs: %d\n", inputIndex); - else - sprintf(output+strlen(output), "I:%d|", inputIndex); + output_ptr += sprintf(output_ptr, verbose?"Inputs: %d\n":"I:%d|", inputIndex); inputIndex = 0; do { @@ -1512,678 +1098,290 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers } Error("Failed to enumerate input %d: %s", input.index, strerror(errno)); if ( verbose ) - sprintf(output, "Error, failed to enumerate input %d: %s\n", input.index, strerror(errno)); + output_ptr += sprintf(output_ptr, "Error, failed to enumerate input %d: %s\n", input.index, strerror(errno)); else - sprintf(output, "error%d\n", errno); + output_ptr += sprintf(output_ptr, "error%d\n", errno); return false; } if ( vidioctl(vid_fd, VIDIOC_S_INPUT, &input.index) < 0 ) { Error("Failed to set video input %d: %s", input.index, strerror(errno)); if ( verbose ) - sprintf(output, "Error, failed to switch to input %d: %s\n", input.index, strerror(errno)); + output_ptr += sprintf(output_ptr, "Error, failed to switch to input %d: %s\n", input.index, strerror(errno)); else - sprintf(output, "error%d\n", errno); + output_ptr += sprintf(output_ptr, "error%d\n", errno); return false; } if ( verbose ) { - sprintf( output+strlen(output), " Input %d\n", input.index ); - sprintf( output+strlen(output), " Name: %s\n", input.name ); - sprintf( output+strlen(output), " Type: %s\n", input.type==V4L2_INPUT_TYPE_TUNER?"Tuner":(input.type==V4L2_INPUT_TYPE_CAMERA?"Camera":"Unknown") ); - sprintf( output+strlen(output), " Audioset: %08x\n", input.audioset ); - sprintf( output+strlen(output), " Standards: 0x%llx\n", input.std ); + output_ptr += sprintf( output, + " Input %d\n" + " Name: %s\n" + " Type: %s\n" + " Audioset: %08x\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 + , static_cast(input.std)); } else { - sprintf( output+strlen(output), "i%d:%s|", input.index, input.name ); - sprintf( output+strlen(output), "i%dT:%s|", input.index, input.type==V4L2_INPUT_TYPE_TUNER?"Tuner":(input.type==V4L2_INPUT_TYPE_CAMERA?"Camera":"Unknown") ); - sprintf( output+strlen(output), "i%dS:%llx|", input.index, input.std ); + 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, static_cast(input.std)); } if ( verbose ) { - sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_POWER, "Power ", "off", "on", " (X)" ) ); - sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_SIGNAL, "Signal ", "not detected", "detected", " (X)" ) ); - sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_COLOR, "Colour Signal ", "not detected", "detected", "" ) ); - sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_H_LOCK, "Horizontal Lock ", "not detected", "detected", "" ) ); + output_ptr += sprintf( output_ptr, " %s %s %s %s" + , capString(input.status&V4L2_IN_ST_NO_POWER, "Power ", "off", "on", " (X)") + , capString(input.status&V4L2_IN_ST_NO_SIGNAL, "Signal ", "not detected", "detected", " (X)") + , capString(input.status&V4L2_IN_ST_NO_COLOR, "Colour Signal ", "not detected", "detected", "") + , capString(input.status&V4L2_IN_ST_NO_H_LOCK, "Horizontal Lock ", "not detected", "detected", "")); } else { - sprintf( output+strlen(output), "i%dSP:%d|", input.index, (input.status&V4L2_IN_ST_NO_POWER)?0:1 ); - sprintf( output+strlen(output), "i%dSS:%d|", input.index, (input.status&V4L2_IN_ST_NO_SIGNAL)?0:1 ); - sprintf( output+strlen(output), "i%dSC:%d|", input.index, (input.status&V4L2_IN_ST_NO_COLOR)?0:1 ); - sprintf( output+strlen(output), "i%dHP:%d|", input.index, (input.status&V4L2_IN_ST_NO_H_LOCK)?0:1 ); + output_ptr += sprintf( output_ptr, "i%dSP:%d|i%dSS:%d|i%dSC:%d|i%dHP:%d|" + , input.index, (input.status&V4L2_IN_ST_NO_POWER)?0:1 + , input.index, (input.status&V4L2_IN_ST_NO_SIGNAL)?0:1 + , input.index, (input.status&V4L2_IN_ST_NO_COLOR)?0:1 + , input.index, (input.status&V4L2_IN_ST_NO_H_LOCK)?0:1 ); } } while ( inputIndex++ >= 0 ); if ( !verbose ) - output[strlen(output)-1] = '\n'; + *(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 ) - sprintf( output, "Error, failed to get video capabilities %s: %s\n", queryDevice, strerror(errno) ); - else - sprintf( output, "error%d\n", errno ); - return( false ); - } - if ( verbose ) { - sprintf( output+strlen(output), "Video Capabilities\n" ); - sprintf( output+strlen(output), " Name: %s\n", vid_cap.name ); - sprintf( output+strlen(output), " Type: %d\n%s%s%s%s%s%s%s%s%s%s%s%s%s%s", 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":"" - ); - sprintf( output+strlen(output), " Video Channels: %d\n", vid_cap.channels ); - sprintf( output+strlen(output), " Audio Channels: %d\n", vid_cap.audios ); - sprintf( output+strlen(output), " Maximum Width: %d\n", vid_cap.maxwidth ); - sprintf( output+strlen(output), " Maximum Height: %d\n", vid_cap.maxheight ); - sprintf( output+strlen(output), " Minimum Width: %d\n", vid_cap.minwidth ); - sprintf( output+strlen(output), " Minimum Height: %d\n", vid_cap.minheight ); - } - else - { - sprintf( output+strlen(output), "N:%s|", vid_cap.name ); - sprintf( output+strlen(output), "T:%d|", vid_cap.type ); - sprintf( output+strlen(output), "nC:%d|", vid_cap.channels ); - sprintf( output+strlen(output), "nA:%d|", vid_cap.audios ); - sprintf( output+strlen(output), "mxW:%d|", vid_cap.maxwidth ); - sprintf( output+strlen(output), "mxH:%d|", vid_cap.maxheight ); - sprintf( output+strlen(output), "mnW:%d|", vid_cap.minwidth ); - sprintf( output+strlen(output), "mnH:%d|", 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 ) - sprintf( output, "Error, failed to get window attributes: %s\n", strerror(errno) ); - else - sprintf( output, "error%d\n", errno ); - return false; - } - if ( verbose ) { - sprintf( output+strlen(output), "Window Attributes\n" ); - sprintf( output+strlen(output), " X Offset: %d\n", vid_win.x ); - sprintf( output+strlen(output), " Y Offset: %d\n", vid_win.y ); - sprintf( output+strlen(output), " Width: %d\n", vid_win.width ); - sprintf( output+strlen(output), " Height: %d\n", vid_win.height ); - } else { - sprintf( output+strlen(output), "X:%d|", vid_win.x ); - sprintf( output+strlen(output), "Y:%d|", vid_win.y ); - sprintf( output+strlen(output), "W:%d|", vid_win.width ); - sprintf( output+strlen(output), "H:%d|", vid_win.height ); - } - - 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 ) - sprintf( output, "Error, failed to get picture attributes: %s\n", strerror(errno) ); - else - sprintf( output, "error%d\n", errno ); - return false; - } - if ( verbose ) { - sprintf( output+strlen(output), "Picture Attributes\n" ); - sprintf( output+strlen(output), " Palette: %d - %s\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" - )))))))))))))))))); - sprintf( output+strlen(output), " Colour Depth: %d\n", vid_pic.depth ); - sprintf( output+strlen(output), " Brightness: %d\n", vid_pic.brightness ); - sprintf( output+strlen(output), " Hue: %d\n", vid_pic.hue ); - sprintf( output+strlen(output), " Colour :%d\n", vid_pic.colour ); - sprintf( output+strlen(output), " Contrast: %d\n", vid_pic.contrast ); - sprintf( output+strlen(output), " Whiteness: %d\n", vid_pic.whiteness ); - } else { - sprintf( output+strlen(output), "P:%d|", vid_pic.palette ); - sprintf( output+strlen(output), "D:%d|", vid_pic.depth ); - sprintf( output+strlen(output), "B:%d|", vid_pic.brightness ); - sprintf( output+strlen(output), "h:%d|", vid_pic.hue ); - sprintf( output+strlen(output), "Cl:%d|", vid_pic.colour ); - sprintf( output+strlen(output), "Cn:%d|", vid_pic.contrast ); - sprintf( output+strlen(output), "w:%d|", 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 ) - sprintf( output, "Error, failed to get channel %d attributes: %s\n", chan, strerror(errno) ); - else - sprintf( output, "error%d\n", errno ); - return false; - } - if ( verbose ) { - sprintf( output+strlen(output), "Channel %d Attributes\n", chan ); - sprintf( output+strlen(output), " Name: %s\n", vid_src.name ); - sprintf( output+strlen(output), " Channel: %d\n", vid_src.channel ); - sprintf( output+strlen(output), " Flags: %d\n%s%s", vid_src.flags, - (vid_src.flags&VIDEO_VC_TUNER)?" Channel has a tuner\n":"", - (vid_src.flags&VIDEO_VC_AUDIO)?" Channel has audio\n":"" - ); - sprintf( output+strlen(output), " Type: %d - %s\n", vid_src.type, - vid_src.type==VIDEO_TYPE_TV?"TV":( - vid_src.type==VIDEO_TYPE_CAMERA?"Camera":"Unknown" - )); - sprintf( output+strlen(output), " Format: %d - %s\n", 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 { - sprintf( output+strlen(output), "n%d:%s|", chan, vid_src.name ); - sprintf( output+strlen(output), "C%d:%d|", chan, vid_src.channel ); - sprintf( output+strlen(output), "Fl%d:%x|", chan, vid_src.flags ); - sprintf( output+strlen(output), "T%d:%d|", chan, vid_src.type ); - sprintf( output+strlen(output), "F%d:%d%s|", chan, vid_src.norm, chan==(vid_cap.channels-1)?"":"," ); - } - } - if ( !verbose ) - output[strlen(output)-1] = '\n'; - } -#endif // ZM_HAS_V4L1 - close( vid_fd ); - if ( device ) + close(vid_fd); + 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; - } - - 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; + return vid_control.value; } -int LocalCamera::Hue( int p_hue ) { -#if ZM_HAS_V4L2 - if ( v4l_version == 2 ) { - struct v4l2_control vid_control; +int LocalCamera::Brightness(int p_brightness) { + return Control(V4L2_CID_BRIGHTNESS, p_brightness); +} - 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; +int LocalCamera::Hue(int p_hue) { + 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() { - Initialise(); + getVideoStream(); + if (!device_prime) + return 1; - Debug(2, "Priming capture"); -#if ZM_HAS_V4L2 - if ( v4l_version == 2 ) { - Debug(3, "Queueing buffers"); - 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)); - - vid_buf.type = v4l2_data.fmt.type; - vid_buf.memory = v4l2_data.reqbufs.memory; - vid_buf.index = frame; - - if ( vidioctl(vid_fd, VIDIOC_QBUF, &vid_buf) < 0 ) - Fatal("Failed to queue buffer %d: %s", frame, strerror(errno)); + 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 = NULL; - 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 ) - Fatal("Failed to start capture stream: %s", strerror(errno)); - } -#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; - return 0; + 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 0; + return 1; } -int LocalCamera::Capture(Image &image) { - Debug(3, "Capturing"); - static uint8_t* buffer = NULL; +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_MEMORY_MMAP; - 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)); - } + 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(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; } - - 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 -#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); + } // while captures_per_frame - buffer = v4l1_data.bufptr+v4l1_data.frames.offsets[capture_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"); } -#endif // ZM_HAS_V4L1 } /* prime capture */ - if ( conversion_type != 0 ) { + if (!zm_packet->image) { + Debug(4, "Allocating image"); + zm_packet->image = new Image(width, height, colours, subpixelorder); + } - Debug(3, "Performing format conversion"); + if (conversion_type != 0) { + Debug(3, "Performing format conversion %d", conversion_type); /* Request a writeable buffer of the target image */ - uint8_t* directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); - if ( directbuffer == NULL ) { + 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, + + sws_scale( + imgConversionContext, capturePictures[capture_frame]->data, capturePictures[capture_frame]->linesize, 0, height, tmpPicture->data, - tmpPicture->linesize ); - } -#endif - if ( conversion_type == 2 ) { + tmpPicture->linesize + ); + } 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); } else if ( conversion_type == 3 ) { + // Need to store the jpeg data too Debug(9, "Decoding the JPEG image"); /* JPEG decoding */ - 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 */ - 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->stream = mVideoStream; + zm_packet->codec_type = AVMEDIA_TYPE_VIDEO; + zm_packet->keyframe = 1; return 1; } // end int LocalCamera::Capture() int LocalCamera::PostCapture() { - Debug(4, "Post-capturing"); - // Requeue the buffer unless we need to switch or are a duplicate camera on a channel - if ( channel_count > 1 || channel_prime ) { -#if ZM_HAS_V4L2 - if ( v4l_version == 2 ) { - if ( channel_count > 1 ) { - 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)); - return -1; - } - } - 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") - } - } -#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 - } - return 0; + return 1; } - -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 diff --git a/src/zm_local_camera.h b/src/zm_local_camera.h index d06631c23..f702b816f 100644 --- a/src/zm_local_camera.h +++ b/src/zm_local_camera.h @@ -20,47 +20,25 @@ #ifndef ZM_LOCAL_CAMERA_H #define ZM_LOCAL_CAMERA_H -#include "zm.h" #include "zm_camera.h" -#include "zm_image.h" -#include "zm_packetqueue.h" -#if ZM_HAS_V4L +#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 - -#include "zm_ffmpeg.h" // // Class representing 'local' cameras, i.e. those which are // directly connect to the host machine and which are accessed // via a video interface. // -class LocalCamera : public Camera -{ +class LocalCamera : public Camera { protected: -#if ZM_HAS_V4L2 - struct V4L2MappedBuffer - { + struct V4L2MappedBuffer { void *start; size_t length; }; - struct V4L2Data - { + struct V4L2Data { v4l2_cropcap cropcap; v4l2_crop crop; v4l2_format fmt; @@ -68,17 +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; @@ -104,26 +71,19 @@ 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; public: LocalCamera( - int p_id, + const Monitor *monitor, const std::string &device, int p_channel, int p_format, @@ -146,28 +106,27 @@ public: void Initialise(); void Terminate(); - const std::string &Device() const { return( device ); } + const std::string &Device() const { return device; } - int Channel() const { return( channel ); } - int Standard() const { return( standard ); } - int Palette() const { return( palette ); } - int Extras() const { return( extras ); } + int Channel() const { return channel; } + int Standard() const { return standard; } + int Palette() const { return palette; } + int Extras() const { return extras; } - int Brightness( int p_brightness=-1 ); - int Hue( int p_hue=-1 ); - int Colour( int p_colour=-1 ); - int Contrast( int p_contrast=-1 ); + int 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; + int Contrast( int p_contrast=-1 ) override; - int PrimeCapture(); - int PreCapture(); - int Capture( Image &image ); - int PostCapture(); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; - int Close() { return 0; }; - - static bool GetCurrentSettings( const char *device, char *output, int version, bool verbose ); + int PrimeCapture() override; + int PreCapture() override; + int Capture(std::shared_ptr &p) override; + int PostCapture() override; + 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 1b8afc517..f43ea6f30 100644 --- a/src/zm_logger.cpp +++ b/src/zm_logger.cpp @@ -19,49 +19,35 @@ #include "zm_logger.h" -#include "zm_config.h" -#include "zm_utils.h" #include "zm_db.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "zm_time.h" +#include "zm_utils.h" #include +#include +#include + #ifdef __FreeBSD__ #include #endif +#include +#include +#include +#include + bool Logger::smInitialised = false; -Logger *Logger::smInstance = NULL; +Logger *Logger::smInstance = nullptr; Logger::StringMap Logger::smCodes; Logger::IntMap Logger::smSyslogPriorities; -#if 0 -static void subtractTime( struct timeval * const tp1, struct timeval * const tp2 ) { - tp1->tv_sec -= tp2->tv_sec; - if ( tp1->tv_usec <= tp2->tv_usec ) { - tp1->tv_sec--; - tp1->tv_usec = 1000000 - (tp2->tv_usec - tp1->tv_usec); - } else { - tp1->tv_usec = tp1->tv_usec - tp2->tv_usec; - } -} -#endif - void Logger::usrHandler(int sig) { Logger *logger = fetch(); - if ( sig == SIGUSR1 ) + 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() : @@ -73,16 +59,15 @@ Logger::Logger() : mEffectiveLevel(NOLOG), mDbConnected(false), mLogPath(staticConfig.PATH_LOGS.c_str()), - //mLogFile( mLogPath+"/"+mId+".log" ), - mLogFileFP(NULL), + // mLogFile( mLogPath+"/"+mId+".log" ), + mLogFileFP(nullptr), mHasTerminal(false), mFlush(false) { - - if ( smInstance ) { + if (smInstance) { Panic("Attempt to create second instance of Logger class"); } - if ( !smInitialised ) { + if (!smInitialised) { smCodes[INFO] = "INF"; smCodes[WARNING] = "WAR"; smCodes[ERROR] = "ERR"; @@ -97,16 +82,16 @@ Logger::Logger() : smSyslogPriorities[PANIC] = LOG_ERR; char code[4] = ""; - for ( int i = DEBUG1; i <= DEBUG9; i++ ) { + for (int i = DEBUG1; i <= DEBUG9; i++) { snprintf(code, sizeof(code), "DB%d", i); smCodes[i] = code; smSyslogPriorities[i] = LOG_DEBUG; } smInitialised = true; - } + } // end if ! smInitialised - if ( fileno(stderr) && isatty(fileno(stderr)) ) { + if (fileno(stderr) && isatty(fileno(stderr))) { mHasTerminal = true; mTerminalLevel = WARNING; } @@ -117,14 +102,6 @@ Logger::~Logger() { smCodes.clear(); smSyslogPriorities.clear(); smInitialised = false; -#if 0 - for ( StringMap::iterator itr = smCodes.begin(); itr != smCodes.end(); itr ++ ) { - smCodes.erase( itr ); - } - for ( IntMap::iterator itr = smSyslogPriorities.begin(); itr != smSyslogPriorities.end(); itr ++ ) { - smSyslogPriorities.erase(itr); - } -#endif } void Logger::initialise(const std::string &id, const Options &options) { @@ -149,23 +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? 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 @@ -188,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 == "" ) { @@ -200,7 +178,7 @@ void Logger::initialise(const std::string &id, const Options &options) { } } } - } // end foreach target + } // end foreach target } else { // if we don't have debug turned on, then the max effective log level is INFO if ( tempSyslogLevel > INFO ) tempSyslogLevel = INFO; @@ -208,7 +186,7 @@ void Logger::initialise(const std::string &id, const Options &options) { if ( tempTerminalLevel > INFO ) tempTerminalLevel = INFO; if ( tempDatabaseLevel > INFO ) tempDatabaseLevel = INFO; if ( tempLevel > INFO ) tempLevel = INFO; - } // end if config.log_debug + } // end if config.log_debug logFile(tempLogFile); @@ -282,9 +260,7 @@ std::string Logger::strEnv(const std::string &name, const std::string &defaultVa } char *Logger::getTargettedEnv(const std::string &name) { - std::string envName; - - envName = name+"_"+mId; + std::string envName = name+"_"+mId; char *envPtr = getenv(envName.c_str()); if ( !envPtr && mId != mIdRoot ) { envName = name+"_"+mIdRoot; @@ -320,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; @@ -348,35 +322,33 @@ Logger::Level Logger::terminalLevel(Logger::Level terminalLevel) { if ( terminalLevel > NOOPT ) { if ( !mHasTerminal ) terminalLevel = NOLOG; - terminalLevel = limit(terminalLevel); - if ( mTerminalLevel != terminalLevel ) - mTerminalLevel = terminalLevel; + mTerminalLevel = limit(terminalLevel); } return mTerminalLevel; } Logger::Level Logger::databaseLevel(Logger::Level databaseLevel) { - if ( databaseLevel > NOOPT ) { + if (databaseLevel > NOOPT) { databaseLevel = limit(databaseLevel); - if ( mDatabaseLevel != databaseLevel ) { - if ( databaseLevel > NOLOG && mDatabaseLevel <= NOLOG ) { - if ( !zmDbConnect() ) { + if (mDatabaseLevel != databaseLevel) { + if ((databaseLevel > NOLOG) && (mDatabaseLevel <= NOLOG)) { // <= NOLOG would be NOOPT + if (!zmDbConnected) { databaseLevel = NOLOG; } - } // end if ( databaseLevel > NOLOG && mDatabaseLevel <= NOLOG ) + } mDatabaseLevel = databaseLevel; - } // end if ( mDatabaseLevel != databaseLevel ) - } // end if ( databaseLevel > NOOPT ) + } + } return mDatabaseLevel; } Logger::Level Logger::fileLevel(Logger::Level fileLevel) { - if ( fileLevel > NOOPT ) { + if (fileLevel > NOOPT) { fileLevel = limit(fileLevel); // Always close, because we may have changed file names - if ( mFileLevel > NOLOG ) - closeFile(); + if (mFileLevel > NOLOG) + closeFile(); mFileLevel = fileLevel; // Don't try to open it here because it will create the log file even if we never write to it. } @@ -384,13 +356,13 @@ Logger::Level Logger::fileLevel(Logger::Level fileLevel) { } Logger::Level Logger::syslogLevel(Logger::Level syslogLevel) { - if ( syslogLevel > NOOPT ) { + if (syslogLevel > NOOPT) { syslogLevel = limit(syslogLevel); - if ( mSyslogLevel != syslogLevel ) { - if ( mSyslogLevel > NOLOG ) + if (mSyslogLevel != syslogLevel) { + if (mSyslogLevel > NOLOG) closeSyslog(); mSyslogLevel = syslogLevel; - if ( mSyslogLevel > NOLOG ) + if (mSyslogLevel > NOLOG) openSyslog(); } } @@ -400,40 +372,39 @@ Logger::Level Logger::syslogLevel(Logger::Level syslogLevel) { void Logger::logFile(const std::string &logFile) { bool addLogPid = false; std::string tempLogFile = logFile; - if ( tempLogFile[tempLogFile.length()-1] == '+' ) { + if (tempLogFile[tempLogFile.length()-1] == '+') { tempLogFile.resize(tempLogFile.length()-1); addLogPid = true; } - if ( addLogPid ) + if (addLogPid) mLogFile = stringtf("%s.%05d", tempLogFile.c_str(), getpid()); else mLogFile = tempLogFile; } void Logger::openFile() { - if ( mLogFile.size() ) { - if ( (mLogFileFP = fopen(mLogFile.c_str(), "a")) == (FILE *)NULL ) { - mFileLevel = NOLOG; - Error("fopen() for %s, error = %s", mLogFile.c_str(), strerror(errno)); - } + if (mLogFile.size()) { + if ( (mLogFileFP = fopen(mLogFile.c_str(), "a")) == nullptr ) { + mFileLevel = NOLOG; + Error("fopen() for %s, error = %s", mLogFile.c_str(), strerror(errno)); + } } else { puts("Called Logger::openFile() without a filename"); } } void Logger::closeFile() { - if ( mLogFileFP ) { + if (mLogFileFP) { fflush(mLogFileFP); - if ( fclose(mLogFileFP) < 0 ) { - mLogFileFP = (FILE *)NULL; + if (fclose(mLogFileFP) < 0) { + mLogFileFP = nullptr; Error("fclose(), error = %s", strerror(errno)); } - mLogFileFP = (FILE *)NULL; + mLogFileFP = nullptr; } } void Logger::closeDatabase() { - } void Logger::openSyslog() { @@ -444,42 +415,30 @@ void Logger::closeSyslog() { (void) closelog(); } -void Logger::logPrint(bool hex, const char * const filepath, const int line, const int level, const char *fstring, ...) { - - if ( level > mEffectiveLevel ) { - return; - } +void Logger::logPrint(bool hex, const char *filepath, int line, int level, const char *fstring, ...) { + if (level > mEffectiveLevel) return; + if (level < PANIC || level > DEBUG9) + Panic("Invalid logger level %d", level); log_mutex.lock(); + // Can we save some cycles by having these as members and not allocate them on the fly? I think so. char timeString[64]; - char logString[8192]; + char logString[4096]; // SQL TEXT can hold 64k so we could go up to 32k here but why? va_list argPtr; - struct timeval timeVal; - char *filecopy = strdup(filepath); - const char * const file = basename(filecopy); + const char *base = strrchr(filepath, '/'); + const char *file = base ? base+1 : filepath; const char *classString = smCodes[level].c_str(); - if ( level < PANIC || level > DEBUG9 ) - Panic("Invalid logger level %d", level); + 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())); - gettimeofday(&timeVal, NULL); - -#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__ @@ -491,12 +450,12 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con #else #ifdef HAVE_SYSCALL #ifdef __FreeBSD_kernel__ - if ( (syscall(SYS_thr_self, &tid)) < 0 ) // Thread/Process id + if ((syscall(SYS_thr_self, &tid)) < 0) // Thread/Process id # else // SOLARIS doesn't have SYS_gettid; don't assume #ifdef SYS_gettid - if ( (tid = syscall(SYS_gettid)) < 0 ) // Thread/Process id + if ((tid = syscall(SYS_gettid)) < 0) // Thread/Process id #endif // SYS_gettid #endif #endif // HAVE_SYSCALL @@ -528,79 +487,70 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con } va_end(argPtr); char *syslogEnd = logPtr; + + if ( static_cast(logPtr - logString) >= sizeof(logString) ) { + // vsnprintf won't exceed the the buffer, but it might hit the end. + logPtr = logString + sizeof(logString)-3; + } strncpy(logPtr, "]\n", sizeof(logString)-(logPtr-logString)); - if ( level <= mTerminalLevel ) { + if (level <= mTerminalLevel) { puts(logString); fflush(stdout); } - if ( level <= mFileLevel ) { - if ( !mLogFileFP ) + + if (level <= mFileLevel) { + if (!mLogFileFP) { + // FIXME unlocking here is a problem. Another thread could sneak in. + // We are using a recursive mutex so unlocking shouldn't be neccessary + //log_mutex.unlock(); + // We do this here so that we only create the file if we ever write to it. openFile(); - if ( mLogFileFP ) { - fputs(logString, mLogFileFP); - if ( mFlush ) - fflush(mLogFileFP); - } else { - puts("Logging to file, but failed to open it\n"); + //log_mutex.lock(); } -#if 0 - } else { - printf("Not writing to log file because level %d %s <= mFileLevel %d %s\nstring: %s\n", - level, smCodes[level].c_str(), mFileLevel, smCodes[mFileLevel].c_str(), logString); -#endif - } - *syslogEnd = '\0'; - if ( level <= mDatabaseLevel ) { - char sql[ZM_SQL_MED_BUFSIZ]; - char escapedString[(strlen(syslogStart)*2)+1]; + if (mLogFileFP) { + fputs(logString, mLogFileFP); + if (mFlush) fflush(mLogFileFP); + } else if (mTerminalLevel != NOLOG) { + puts("Logging to file but failed to open it\n"); + } + } // end if level <= mFileLevel - if ( !db_mutex.trylock() ) { - mysql_real_escape_string(&dbconn, escapedString, syslogStart, strlen(syslogStart)); + if (level <= mDatabaseLevel) { + if (zmDbConnected) { + std::string escapedString = zmDbEscapeString({syslogStart, syslogEnd}); - snprintf(sql, sizeof(sql), + std::string sql_string = stringtf( "INSERT INTO `Logs` " "( `TimeKey`, `Component`, `ServerId`, `Pid`, `Level`, `Code`, `Message`, `File`, `Line` )" - " VALUES " - "( %ld.%06ld, '%s', %d, %d, %d, '%s', '%s', '%s', %d )", - timeVal.tv_sec, timeVal.tv_usec, mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, escapedString, file, line - ); - if ( mysql_query(&dbconn, sql) ) { - Level tempDatabaseLevel = mDatabaseLevel; - databaseLevel(NOLOG); - Error("Can't insert log entry: sql(%s) error(%s)", sql, mysql_error(&dbconn)); - databaseLevel(tempDatabaseLevel); - } - db_mutex.unlock(); + " VALUES " + "( %ld.%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 { - Level tempDatabaseLevel = mDatabaseLevel; - databaseLevel(NOLOG); - Error("Can't insert log entry: sql(%s) error(db is locked)", logString); - databaseLevel(tempDatabaseLevel); + puts("Db is closed"); } - } - if ( level <= mSyslogLevel ) { - int priority = smSyslogPriorities[level]; - //priority |= LOG_DAEMON; - syslog(priority, "%s [%s] [%s]", classString, mId.c_str(), syslogStart); + } // end if level <= mDatabaseLevel + + if (level <= mSyslogLevel) { + *syslogEnd = '\0'; + syslog(smSyslogPriorities[level], "%s [%s] [%s]", classString, mId.c_str(), syslogStart); } - free(filecopy); - if ( level <= FATAL ) { - log_mutex.unlock(); + log_mutex.unlock(); + if (level <= FATAL) { logTerm(); zmDbClose(); - if ( level <= PANIC ) - abort(); + if (level <= PANIC) abort(); exit(-1); } - log_mutex.unlock(); } // end logPrint void logInit(const char *name, const Logger::Options &options) { - if ( Logger::smInstance ) { + if (Logger::smInstance) { delete Logger::smInstance; - Logger::smInstance = NULL; + Logger::smInstance = nullptr; } Logger::smInstance = new Logger(); @@ -608,8 +558,8 @@ void logInit(const char *name, const Logger::Options &options) { } void logTerm() { - if ( Logger::smInstance ) { + if (Logger::smInstance) { delete Logger::smInstance; - Logger::smInstance = NULL; + Logger::smInstance = nullptr; } } diff --git a/src/zm_logger.h b/src/zm_logger.h index 82144a1c9..d5a01be39 100644 --- a/src/zm_logger.h +++ b/src/zm_logger.h @@ -20,17 +20,16 @@ #ifndef ZM_LOGGER_H #define ZM_LOGGER_H +#include "zm_db.h" #include "zm_config.h" -#include -#include -#include +#include "zm_define.h" #include +#include +#include + #ifdef HAVE_SYS_SYSCALL_H #include #endif // HAVE_SYS_SYSCALL_H -#include - -#include "zm_thread.h" class Logger { public: @@ -90,7 +89,7 @@ private: static bool smInitialised; static Logger *smInstance; - RecursiveMutex log_mutex; + std::recursive_mutex log_mutex; static StringMap smCodes; static IntMap smSyslogPriorities; @@ -121,7 +120,7 @@ private: Logger(); ~Logger(); - int limit(int level) { + int limit(const int level) const { if ( level > DEBUG9 ) return DEBUG9; if ( level < NOLOG ) @@ -154,18 +153,12 @@ public: void terminate(); const std::string &id(const std::string &id); - const std::string &id() const { - return mId; - } + const std::string &id() const { return mId; } - Level level() const { - return mLevel; - } + Level level() const { return mLevel; } Level level(Level=NOOPT); - bool debugOn() { - return mEffectiveLevel >= DEBUG1; - } + bool debugOn() const { return mEffectiveLevel >= DEBUG1; } Level terminalLevel(Level=NOOPT); Level databaseLevel(Level=NOOPT); @@ -180,8 +173,13 @@ private: void closeSyslog(); void closeDatabase(); -public: - void logPrint(bool hex, const char * const filepath, const int line, const int level, const char *fstring, ...); + public: + void logPrint(bool hex, + const char *filepath, + int line, + int level, + const char *fstring, + ...) __attribute__((format(printf, 6, 7))); }; void logInit(const char *name, const Logger::Options &options=Logger::Options()); @@ -196,15 +194,19 @@ inline Logger::Level logDebugging() { return Logger::fetch()->debugOn(); } -#define logPrintf(logLevel,params...) {\ - if ( logLevel <= Logger::fetch()->level() )\ - Logger::fetch()->logPrint( false, __FILE__, __LINE__, logLevel, ##params );\ - } +#define logPrintf(logLevel, params...) \ + do { \ + if (logLevel <= Logger::fetch()->level()) { \ + Logger::fetch()->logPrint(false, __FILE__, __LINE__, logLevel, ##params); \ + } \ + } while (0) -#define logHexdump(logLevel,data,len) {\ - if ( logLevel <= Logger::fetch()->level() )\ - Logger::fetch()->logPrint( true, __FILE__, __LINE__, logLevel, "%p (%d)", data, len );\ - } +#define logHexdump(logLevel, data, len) \ + do { \ + if (logLevel <= Logger::fetch()->level()) { \ + Logger::fetch()->logPrint(true, __FILE__, __LINE__, logLevel, "%p (%d)", data, len); \ + } \ + } while (0) /* Debug compiled out */ #ifndef DBG_OFF diff --git a/src/zm_mem_utils.h b/src/zm_mem_utils.h index 8d841d670..72fb2b9aa 100644 --- a/src/zm_mem_utils.h +++ b/src/zm_mem_utils.h @@ -20,26 +20,25 @@ #ifndef ZM_MEM_UTILS_H #define ZM_MEM_UTILS_H -#include -#include "zm.h" +#include inline void* zm_mallocaligned(unsigned int reqalignment, size_t reqsize) { uint8_t* retptr; #if HAVE_POSIX_MEMALIGN - if ( posix_memalign((void**)&retptr,reqalignment,reqsize) != 0 ) - return NULL; + if ( posix_memalign((void**)&retptr, reqalignment, reqsize) != 0 ) + return nullptr; return retptr; #else uint8_t* alloc; retptr = (uint8_t*)malloc(reqsize+reqalignment+sizeof(void*)); - if ( retptr == NULL ) - return NULL; + if ( retptr == nullptr ) + return nullptr; alloc = retptr + sizeof(void*); - if(((long)alloc % reqalignment) != 0) + if ( ((long)alloc % reqalignment) != 0 ) alloc = alloc + (reqalignment - ((long)alloc % reqalignment)); /* Store a pointer before to the start of the block, just before returned aligned memory */ @@ -60,7 +59,7 @@ inline void zm_freealigned(void* ptr) { inline char *mempbrk(const char *s, const char *accept, size_t limit) { if ( limit == 0 || !s || !accept || !*accept ) - return 0; + return nullptr; unsigned int i,j; size_t acc_len = strlen(accept); @@ -72,12 +71,12 @@ inline char *mempbrk(const char *s, const char *accept, size_t limit) { } } } - return 0; + return nullptr; } inline char *memstr(const char *s, const char *n, size_t limit) { if ( limit == 0 || !s || !n ) - return 0; + return nullptr; if ( !*n ) return (char *)s; @@ -97,7 +96,7 @@ inline char *memstr(const char *s, const char *n, size_t limit) { break; } } - return 0; + return nullptr; } inline size_t memspn(const char *s, const char *accept, size_t limit) { diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 2f0e4826b..062e10ac7 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -17,41 +17,43 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include -#include -#include -#include -#include - -#include "zm.h" -#include "zm_db.h" -#include "zm_time.h" -#include "zm_mpeg.h" -#include "zm_signal.h" #include "zm_monitor.h" -#include "zm_video.h" + +#include "zm_group.h" #include "zm_eventstream.h" -#if ZM_HAS_V4L -#include "zm_local_camera.h" -#endif // ZM_HAS_V4L +#include "zm_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" -#if HAVE_LIBAVFORMAT #include "zm_remote_camera_rtsp.h" -#endif // HAVE_LIBAVFORMAT -#include "zm_file_camera.h" -#if HAVE_LIBAVFORMAT -#include "zm_ffmpeg_camera.h" -#endif // HAVE_LIBAVFORMAT -#include "zm_fifo.h" +#include "zm_signal.h" +#include "zm_time.h" +#include "zm_utils.h" +#include "zm_zone.h" + +#if ZM_HAS_V4L2 +#include "zm_local_camera.h" +#endif // ZM_HAS_V4L2 + #if HAVE_LIBVLC #include "zm_libvlc_camera.h" #endif // HAVE_LIBVLC + #if HAVE_LIBCURL #include "zm_curl_camera.h" #endif // HAVE_LIBCURL +#if HAVE_LIBVNC +#include "zm_libvnc_camera.h" +#endif // HAVE_LIBVNC + +#include +#include +#include +#include + #if ZM_MEM_MAPPED #include #include @@ -65,405 +67,826 @@ #define MAP_LOCKED 0 #endif +#ifdef WITH_GSOAP +//Workaround for the gsoap library on RHEL +struct Namespace namespaces[] = +{ + {NULL, NULL} // end of table +}; +#endif + // This is the official SQL (and ordering of the fields) to load a Monitor. // It will be used whereever a Monitor dbrow is needed. WHERE conditions can be appended std::string load_monitor_sql = -"SELECT `Id`, `Name`, `ServerId`, `StorageId`, `Type`, `Function`+0, `Enabled`, `LinkedMonitors`, " -"`AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`," +"SELECT `Id`, `Name`, `ServerId`, `StorageId`, `Type`, `Function`+0, `Enabled`, `DecodingEnabled`, `JanusEnabled`, `JanusAudioEnabled`," +"`LinkedMonitors`, `EventStartCommand`, `EventEndCommand`, `AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`," "`Device`, `Channel`, `Format`, `V4LMultiBuffer`, `V4LCapturesPerFrame`, " // V4L Settings -"`Protocol`, `Method`, `Options`, `User`, `Pass`, `Host`, `Port`, `Path`, `Width`, `Height`, `Colours`, `Palette`, `Orientation`+0, `Deinterlacing`, " +"`Protocol`, `Method`, `Options`, `User`, `Pass`, `Host`, `Port`, `Path`, `SecondPath`, `Width`, `Height`, `Colours`, `Palette`, `Orientation`+0, `Deinterlacing`, " "`DecoderHWAccelName`, `DecoderHWAccelDevice`, `RTSPDescribe`, " "`SaveJPEGs`, `VideoWriter`, `EncoderParameters`, " -//" OutputCodec, Encoder, OutputContainer, " +"`OutputCodec`, `Encoder`, `OutputContainer`, " "`RecordAudio`, " "`Brightness`, `Contrast`, `Hue`, `Colour`, " "`EventPrefix`, `LabelFormat`, `LabelX`, `LabelY`, `LabelSize`," -"`ImageBufferCount`, `WarmupCount`, `PreEventCount`, `PostEventCount`, `StreamReplayBuffer`, `AlarmFrameCount`, " +"`ImageBufferCount`, `MaxImageBufferCount`, `WarmupCount`, `PreEventCount`, `PostEventCount`, `StreamReplayBuffer`, `AlarmFrameCount`, " "`SectionLength`, `MinSectionLength`, `FrameSkip`, `MotionFrameSkip`, " -"`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`, `SignalCheckPoints`, `SignalCheckColour` FROM `Monitors`"; +"`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`," +"`RTSPServer`, `RTSPStreamName`," +"`ONVIF_URL`, `ONVIF_Username`, `ONVIF_Password`, `ONVIF_Options`, `ONVIF_Event_Listener`, `use_Amcrest_API`, " +"`SignalCheckPoints`, `SignalCheckColour`, `Importance`-1 FROM `Monitors`"; std::string CameraType_Strings[] = { + "Unknown", "Local", "Remote", "File", "Ffmpeg", "LibVLC", + "NVSOCKET", "CURL", + "VNC" }; +std::string Function_Strings[] = { + "Unknown", + "None", + "Monitor", + "Modect", + "Record", + "Mocord", + "Nodect" +}; -std::vector split(const std::string &s, char delim) { - std::vector elems; - std::stringstream ss(s); - std::string item; - while(std::getline(ss, item, delim)) { - elems.push_back(trimSpaces(item)); - } - return elems; -} +std::string State_Strings[] = { + "Unknown", + "IDLE", + "PREALARM", + "ALARM", + "ALERT", + "TAPE" +}; -Monitor::MonitorLink::MonitorLink( int p_id, const char *p_name ) : - id( p_id ), - shared_data(NULL), - trigger_data(NULL), - video_store_data(NULL) -{ - strncpy( name, p_name, sizeof(name)-1 ); +std::string TriggerState_Strings[] = { + "Cancel", "On", "Off" +}; -#if ZM_MEM_MAPPED - map_fd = -1; - snprintf( mem_file, sizeof(mem_file), "%s/zm.mmap.%d", staticConfig.PATH_MAP.c_str(), id ); -#else // ZM_MEM_MAPPED - shm_id = 0; -#endif // ZM_MEM_MAPPED - mem_size = 0; - mem_ptr = 0; +Monitor::Monitor() + : id(0), + name(""), + server_id(0), + storage_id(0), + type(LOCAL), + function(NONE), + enabled(false), + decoding_enabled(false), + janus_enabled(false), + janus_audio_enabled(false), + //protocol + //method + //options + //host + //port + //user + //pass + //path + //device + palette(0), + channel(0), + format(0), - last_event = 0; - last_state = IDLE; + width(0), + height(0), + //v4l_multi_buffer + //v4l_captures_per_frame + orientation(ROTATE_0), + deinterlacing(0), + deinterlacing_value(0), + decoder_hwaccel_name(""), + decoder_hwaccel_device(""), + videoRecording(false), + rtsp_describe(false), - last_connect_time = 0; - connected = false; -} - -Monitor::MonitorLink::~MonitorLink() { - disconnect(); -} - -bool Monitor::MonitorLink::connect() { - if ( !last_connect_time || (time( 0 ) - last_connect_time) > 60 ) { - last_connect_time = time( 0 ); - - mem_size = sizeof(SharedData) + sizeof(TriggerData); - - Debug( 1, "link.mem.size=%d", 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) ); - disconnect(); - return( false ); - } - 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); - map_fd = new_map_fd; - } - - 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) ); - disconnect(); - return( false ); - } - - if ( map_stat.st_size == 0 ) { - Error( "Linked memory map file %s is empty: %s", mem_file, 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 ); - disconnect(); - return( false ); - } - - mem_ptr = (unsigned char *)mmap( NULL, 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) ); - disconnect(); - return( false ); - } -#else // ZM_MEM_MAPPED - shm_id = shmget( (config.shm_key&0xffff0000)|id, mem_size, 0700 ); - if ( shm_id < 0 ) { - Debug( 3, "Can't shmget link memory: %s", strerror(errno) ); - connected = false; - return( false ); - } - mem_ptr = (unsigned char *)shmat( shm_id, 0, 0 ); - if ( mem_ptr < (void *)0 ) { - Debug( 3, "Can't shmat link memory: %s", strerror(errno) ); - connected = false; - return( false ); - } -#endif // ZM_MEM_MAPPED - - shared_data = (SharedData *)mem_ptr; - trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData)); - - if ( !shared_data->valid ) { - Debug( 3, "Linked memory not initialised by capture daemon" ); - disconnect(); - return( false ); - } - - last_state = shared_data->state; - last_event = shared_data->last_event; - connected = true; - - return( true ); - } - return( false ); -} - -bool Monitor::MonitorLink::disconnect() { - 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 ( 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 ); - } - - 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 ( shmdt( mem_ptr ) < 0 ) { - Debug( 3, "Can't shmdt: %s", strerror(errno) ); - return( false ); - } - -#endif // ZM_MEM_MAPPED - mem_size = 0; - mem_ptr = 0; - } - return( true ); -} - -bool Monitor::MonitorLink::isAlarmed() { - if ( !connected ) { - return( false ); - } - return( shared_data->state == ALARM ); -} - -bool Monitor::MonitorLink::inAlarm() { - if ( !connected ) { - return( false ); - } - return( shared_data->state == ALARM || shared_data->state == ALERT ); -} - -bool Monitor::MonitorLink::hasAlarmed() { - if ( shared_data->state == ALARM ) { - return true; - } else if ( shared_data->last_event != last_event ) { - last_event = shared_data->last_event; - } - return false; -} - -Monitor::Monitor( - int p_id, - const char *p_name, - const unsigned int p_server_id, - const unsigned int p_storage_id, - int p_function, - bool p_enabled, - const char *p_linked_monitors, - Camera *p_camera, - int p_orientation, - unsigned int p_deinterlacing, - const std::string &p_decoder_hwaccel_name, - const std::string &p_decoder_hwaccel_device, - int p_savejpegs, - VideoWriter p_videowriter, - std::string p_encoderparams, - bool p_record_audio, - const char *p_event_prefix, - const char *p_label_format, - const Coord &p_label_coord, - int p_label_size, - int p_image_buffer_count, - int p_warmup_count, - int p_pre_event_count, - int p_post_event_count, - int p_stream_replay_buffer, - int p_alarm_frame_count, - int p_section_length, - int p_min_section_length, - int p_frame_skip, - int p_motion_frame_skip, - double p_capture_max_fps, - double p_analysis_fps, - unsigned int p_analysis_update_delay, - int p_capture_delay, - int p_alarm_capture_delay, - int p_fps_report_interval, - int p_ref_blend_perc, - int p_alarm_ref_blend_perc, - bool p_track_motion, - int p_signal_check_points, - Rgb p_signal_check_colour, - bool p_embed_exif, - Purpose p_purpose, - int p_n_zones, - Zone *p_zones[] -) : id( p_id ), - server_id( p_server_id ), - storage_id( p_storage_id ), - function( (Function)p_function ), - enabled( p_enabled ), - width( (p_orientation==ROTATE_90||p_orientation==ROTATE_270)?p_camera->Height():p_camera->Width() ), - height( (p_orientation==ROTATE_90||p_orientation==ROTATE_270)?p_camera->Width():p_camera->Height() ), - orientation( (Orientation)p_orientation ), - deinterlacing( p_deinterlacing ), - decoder_hwaccel_name(p_decoder_hwaccel_name), - decoder_hwaccel_device(p_decoder_hwaccel_device), - savejpegs( p_savejpegs ), - videowriter( p_videowriter ), - encoderparams( p_encoderparams ), - record_audio( p_record_audio ), - label_coord( p_label_coord ), - label_size( p_label_size ), - image_buffer_count( p_image_buffer_count ), - warmup_count( p_warmup_count ), - pre_event_count( p_pre_event_count ), - post_event_count( p_post_event_count ), - video_buffer_duration({0}), - stream_replay_buffer( p_stream_replay_buffer ), - section_length( p_section_length ), - min_section_length( p_min_section_length ), - frame_skip( p_frame_skip ), - motion_frame_skip( p_motion_frame_skip ), - capture_max_fps( p_capture_max_fps ), - analysis_fps( p_analysis_fps ), - analysis_update_delay( p_analysis_update_delay ), - capture_delay( p_capture_delay ), - alarm_capture_delay( p_alarm_capture_delay ), - alarm_frame_count( p_alarm_frame_count ), - fps_report_interval( p_fps_report_interval ), - ref_blend_perc( p_ref_blend_perc ), - alarm_ref_blend_perc( p_alarm_ref_blend_perc ), - track_motion( p_track_motion ), - signal_check_points(p_signal_check_points), - signal_check_colour( p_signal_check_colour ), - embed_exif( p_embed_exif ), - delta_image( width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE ), - ref_image( width, height, p_camera->Colours(), p_camera->SubpixelOrder() ), - purpose( p_purpose ), + savejpegs(0), + colours(0), + videowriter(DISABLED), + encoderparams(""), + output_codec(0), + encoder(""), + output_container(""), + imagePixFormat(AV_PIX_FMT_NONE), + record_audio(false), +//event_prefix +//label_format + 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), + stream_replay_buffer(0), + section_length(0), + min_section_length(0), + adaptive_skip(false), + frame_skip(0), + motion_frame_skip(0), + analysis_fps_limit(0), + analysis_update_delay(0), + capture_delay(0), + alarm_capture_delay(0), + alarm_frame_count(0), + alert_to_alarm_frame_count(0), + fps_report_interval(0), + ref_blend_perc(0), + alarm_ref_blend_perc(0), + track_motion(false), + signal_check_points(0), + signal_check_colour(0), + embed_exif(false), + rtsp_server(false), + rtsp_streamname(""), + importance(0), + capture_max_fps(0), + purpose(QUERY), + last_camera_bytes(0), + event_count(0), + image_count(0), + last_capture_image_count(0), + analysis_image_count(0), + motion_frame_count(0), + last_motion_frame_count(0), + ready_count(0), + first_alarm_count(0), + last_alarm_count(0), + last_signal(false), + buffer_count(0), + state(IDLE), last_motion_score(0), - camera( p_camera ), - n_zones( p_n_zones ), - zones( p_zones ), - timestamps( 0 ), - images( 0 ), - privacy_bitmask( NULL ), - event_delete_thread(NULL) + event_close_mode(CLOSE_IDLE), +#if ZM_MEM_MAPPED + map_fd(-1), + mem_file(""), +#else // ZM_MEM_MAPPED + shm_id(-1), +#endif // ZM_MEM_MAPPED + mem_size(0), + mem_ptr(nullptr), + shared_data(nullptr), + trigger_data(nullptr), + video_store_data(nullptr), + shared_timestamps(nullptr), + shared_images(nullptr), + video_stream_id(-1), + audio_stream_id(-1), + video_fifo(nullptr), + audio_fifo(nullptr), + camera(nullptr), + event(nullptr), + storage(nullptr), + videoStore(nullptr), + analysis_it(nullptr), + analysis_thread(nullptr), + decoder_it(nullptr), + decoder(nullptr), + dest_frame(nullptr), + convert_context(nullptr), + //zones(nullptr), + privacy_bitmask(nullptr), + n_linked_monitors(0), + linked_monitors(nullptr), + Event_Poller_Closes_Event(FALSE), + Janus_Manager(nullptr), + Amcrest_Manager(nullptr), +#ifdef WITH_GSOAP + soap(nullptr), +#endif + red_val(0), + green_val(0), + blue_val(0), + grayscale_val(0), + colour_val(0) { - if (analysis_fps > 0.0) { - uint64_t usec = round(1000000*pre_event_count/analysis_fps); - video_buffer_duration.tv_sec = usec/1000000; - video_buffer_duration.tv_usec = usec % 1000000; - } - strncpy(name, p_name, sizeof(name)-1); - - strncpy(event_prefix, p_event_prefix, sizeof(event_prefix)-1); - strncpy(label_format, p_label_format, sizeof(label_format)-1); - - // Change \n to actual line feeds - char *token_ptr = label_format; - const char *token_string = "\n"; - while ( ( token_ptr = strstr(token_ptr, token_string) ) ) { - if ( *(token_ptr+1) ) { - *token_ptr = '\n'; - token_ptr++; - strcpy(token_ptr, token_ptr+1); - } else { - *token_ptr = '\0'; - break; - } - } - - /* Parse encoder parameters */ - ParseEncoderParameters(encoderparams.c_str(), &encoderparamsvec); - - fps = 0.0; - last_camera_bytes = 0; - event_count = 0; - image_count = 0; - ready_count = warmup_count; - first_alarm_count = 0; - last_alarm_count = 0; - state = IDLE; - - if ( alarm_frame_count < 1 ) - alarm_frame_count = 1; - else if ( alarm_frame_count > MAX_PRE_ALARM_FRAMES ) - alarm_frame_count = MAX_PRE_ALARM_FRAMES; - - auto_resume_time = 0; - - if ( strcmp( config.event_close_mode, "time" ) == 0 ) + if ( strcmp(config.event_close_mode, "time") == 0 ) event_close_mode = CLOSE_TIME; - else if ( strcmp( config.event_close_mode, "alarm" ) == 0 ) + else if ( strcmp(config.event_close_mode, "alarm") == 0 ) event_close_mode = CLOSE_ALARM; else event_close_mode = CLOSE_IDLE; - Debug( 1, "monitor purpose=%d", purpose ); + event = nullptr; + + adaptive_skip = true; + + videoStore = nullptr; +} // Monitor::Monitor + +/* + std::string load_monitor_sql = + "SELECT Id, Name, ServerId, StorageId, Type, Function+0, Enabled, DecodingEnabled, JanusEnabled, JanusAudioEnabled, LinkedMonitors, `EventStartCommand`, `EventEndCommand`, " + "AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS," + "Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, " // V4L Settings + "Protocol, Method, Options, User, Pass, Host, Port, Path, SecondPath, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, " + "SaveJPEGs, VideoWriter, EncoderParameters, + "OutputCodec, Encoder, OutputContainer, RecordAudio, " + "Brightness, Contrast, Hue, Colour, " + "EventPrefix, LabelFormat, LabelX, LabelY, LabelSize," + "ImageBufferCount, `MaxImageBufferCount`, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, " + "SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, " + "FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif," + "`RTSPServer`,`RTSPStreamName`, + "`ONVIF_URL`, `ONVIF_Username`, `ONVIF_Password`, `ONVIF_Options`, `ONVIF_Event_Listener`, `use_Amcrest_API`, " + "SignalCheckPoints, SignalCheckColour, Importance-1 FROM Monitors"; +*/ + +void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { + purpose = p; + int col = 0; + + id = atoi(dbrow[col]); col++; + name = dbrow[col]; col++; + server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++; + + storage_id = atoi(dbrow[col]); col++; + delete storage; + storage = new Storage(storage_id); + + if ( ! strcmp(dbrow[col], "Local") ) { + type = LOCAL; + } else if ( ! strcmp(dbrow[col], "Ffmpeg") ) { + type = FFMPEG; + } else if ( ! strcmp(dbrow[col], "Remote") ) { + type = REMOTE; + } else if ( ! strcmp(dbrow[col], "File") ) { + type = FILE; + } else if ( ! strcmp(dbrow[col], "NVSocket") ) { + type = NVSOCKET; + } else if ( ! strcmp(dbrow[col], "Libvlc") ) { + type = LIBVLC; + } else if ( ! strcmp(dbrow[col], "cURL") ) { + type = LIBCURL; + } else if ( ! strcmp(dbrow[col], "VNC") ) { + type = VNC; + } else { + Fatal("Bogus monitor type '%s' for monitor %d", dbrow[col], id); + } + Debug(1, "Have camera type %s", CameraType_Strings[type].c_str()); + col++; + function = (Function)atoi(dbrow[col]); col++; + enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; + decoding_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; + // See below after save_jpegs for a recalculation of decoding_enabled + janus_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; + janus_audio_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; + + ReloadLinkedMonitors(dbrow[col]); col++; + event_start_command = dbrow[col] ? dbrow[col] : ""; col++; + event_end_command = dbrow[col] ? dbrow[col] : ""; col++; + + /* "AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS," */ + analysis_fps_limit = dbrow[col] ? strtod(dbrow[col], nullptr) : 0.0; col++; + 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; + if ( dbrow[col] ) { + if (*dbrow[col] == '0' ) { + v4l_multi_buffer = false; + } else if ( *dbrow[col] == '1' ) { + v4l_multi_buffer = true; + } + } + col++; + + v4l_captures_per_frame = 0; + if ( dbrow[col] ) { + v4l_captures_per_frame = atoi(dbrow[col]); + } else { + v4l_captures_per_frame = config.captures_per_frame; + } + col++; + + /* "Protocol, Method, Options, User, Pass, Host, Port, Path, SecondPath, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, " */ + protocol = dbrow[col] ? dbrow[col] : ""; col++; + method = dbrow[col] ? dbrow[col] : ""; col++; + options = dbrow[col] ? dbrow[col] : ""; col++; + user = dbrow[col] ? dbrow[col] : ""; col++; + pass = dbrow[col] ? dbrow[col] : ""; col++; + host = dbrow[col] ? dbrow[col] : ""; col++; + port = dbrow[col] ? dbrow[col] : ""; col++; + path = dbrow[col] ? dbrow[col] : ""; col++; + second_path = dbrow[col] ? dbrow[col] : ""; col++; + camera_width = atoi(dbrow[col]); col++; + camera_height = atoi(dbrow[col]); col++; + colours = atoi(dbrow[col]); col++; + palette = atoi(dbrow[col]); col++; + orientation = (Orientation)atoi(dbrow[col]); col++; + width = (orientation==ROTATE_90||orientation==ROTATE_270) ? camera_height : camera_width; + height = (orientation==ROTATE_90||orientation==ROTATE_270) ? camera_width : camera_height; + deinterlacing = atoi(dbrow[col]); col++; + deinterlacing_value = deinterlacing & 0xff; + +/*"`DecoderHWAccelName`, `DecoderHWAccelDevice`, `RTSPDescribe`, " */ + decoder_hwaccel_name = dbrow[col] ? dbrow[col] : ""; col++; + decoder_hwaccel_device = dbrow[col] ? dbrow[col] : ""; col++; + rtsp_describe = (dbrow[col] && *dbrow[col] != '0'); col++; + + +/* "`SaveJPEGs`, `VideoWriter`, `EncoderParameters`, " */ + savejpegs = atoi(dbrow[col]); col++; + videowriter = (VideoWriter)atoi(dbrow[col]); col++; + encoderparams = dbrow[col] ? dbrow[col] : ""; col++; + + 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++; + + /* "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.setPreEventVideoPackets(pre_event_count); + packetqueue.setMaxVideoPackets(max_image_buffer_count); + packetqueue.setKeepKeyframes(videowriter == PASSTHROUGH); + post_event_count = atoi(dbrow[col]); col++; + stream_replay_buffer = atoi(dbrow[col]); col++; + alarm_frame_count = atoi(dbrow[col]); col++; + if (alarm_frame_count < 1) alarm_frame_count = 1; + else if (alarm_frame_count > MAX_PRE_ALARM_FRAMES) alarm_frame_count = MAX_PRE_ALARM_FRAMES; + + /* "SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, " */ + section_length = 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++; + + onvif_url = std::string(dbrow[col] ? dbrow[col] : ""); col++; + onvif_username = std::string(dbrow[col] ? dbrow[col] : ""); col++; + onvif_password = std::string(dbrow[col] ? dbrow[col] : ""); col++; + onvif_options = std::string(dbrow[col] ? dbrow[col] : ""); col++; + onvif_event_listener = (*dbrow[col] != '0'); col++; + use_Amcrest_API = (*dbrow[col] != '0'); 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); + + last_alarm_count = 0; + state = IDLE; + last_signal = true; // Defaulting to having signal so that we don't get a signal change on the first frame. + // Instead initial failure to capture will cause a loss of signal change which I think makes more sense. + uint64_t image_size = width * height * colours; + + if ( strcmp(config.event_close_mode, "time") == 0 ) + event_close_mode = CLOSE_TIME; + else if ( strcmp(config.event_close_mode, "alarm") == 0 ) + event_close_mode = CLOSE_ALARM; + else + event_close_mode = CLOSE_IDLE; mem_size = sizeof(SharedData) + sizeof(TriggerData) + sizeof(VideoStoreData) //Information to pass back to the capture process + (image_buffer_count*sizeof(struct timeval)) - + (image_buffer_count*camera->ImageSize()) + + (image_buffer_count*image_size) + + image_size // alarm_image + 64; /* Padding used to permit aligning the images buffer to 64 byte boundary */ - Debug(1, "mem.size(%d) SharedData=%d TriggerData=%d VideoStoreData=%d timestamps=%d images=%dx%d = %" PRId64 " total=%" PRId64, - sizeof(mem_size), - sizeof(SharedData), sizeof(TriggerData), sizeof(VideoStoreData), - (image_buffer_count*sizeof(struct timeval)), - image_buffer_count, camera->ImageSize(), (image_buffer_count*camera->ImageSize()), - mem_size); - mem_ptr = NULL; + Debug(1, + "mem.size(%zu) SharedData=%zu TriggerData=%zu VideoStoreData=%zu timestamps=%zu images=%dx%" PRIi64 " = %" PRId64 " total=%jd", + sizeof(mem_size), + sizeof(SharedData), + sizeof(TriggerData), + sizeof(VideoStoreData), + (image_buffer_count * sizeof(struct timeval)), + image_buffer_count, + image_size, + (image_buffer_count * image_size), + mem_size); - storage = new Storage(storage_id); - Debug(1, "Storage path: %s", storage->Path()); // Should maybe store this for later use - char monitor_dir[PATH_MAX] = ""; - snprintf(monitor_dir, sizeof(monitor_dir), "%s/%d", storage->Path(), id); + std::string monitor_dir = stringtf("%s/%u", storage->Path(), id); - if ( purpose == CAPTURE ) { - if ( mkdir(monitor_dir, 0755) ) { - if ( errno != EEXIST ) { - Error("Can't mkdir %s: %s", monitor_dir, strerror(errno)); + if ( purpose != QUERY ) { + LoadCamera(); + ReloadZones(); + + if ( mkdir(monitor_dir.c_str(), 0755) && ( errno != EEXIST ) ) { + Error("Can't mkdir %s: %s", monitor_dir.c_str(), strerror(errno)); + } + + if ( config.record_diag_images ) { + if ( config.record_diag_images_fifo ) { + diag_path_ref = stringtf("%s/diagpipe-r-%d.jpg", staticConfig.PATH_SOCKS.c_str(), id); + diag_path_delta = stringtf("%s/diagpipe-d-%d.jpg", staticConfig.PATH_SOCKS.c_str(), id); + Fifo::fifo_create_if_missing(diag_path_ref.c_str()); + Fifo::fifo_create_if_missing(diag_path_delta.c_str()); + } else { + diag_path_ref = stringtf("%s/%d/diag-r.jpg", storage->Path(), id); + diag_path_delta = stringtf("%s/%d/diag-d.jpg", storage->Path(), id); } } + } // end if purpose - if ( ! this->connect() ) { - Error("unable to connect, but doing capture"); - exit(-1); + Debug(1, "Loaded monitor %d(%s), %zu zones", id, name.c_str(), zones.size()); +} // Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) + +void Monitor::LoadCamera() { + if (camera) + return; + + switch (type) { + case LOCAL: { +#if ZM_HAS_V4L2 + int extras = (deinterlacing >> 24) & 0xff; + + camera = zm::make_unique(this, + device, + channel, + format, + v4l_multi_buffer, + v4l_captures_per_frame, + method, + camera_width, + camera_height, + colours, + palette, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio, + extras + ); +#else + Fatal("Not compiled with local v4l camera support"); +#endif + break; } + case REMOTE: { + if (protocol == "http") { + camera = zm::make_unique(this, + method, + host, + port, + path, + camera_width, + camera_height, + colours, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio + ); + } + else if (protocol == "rtsp") { + camera = zm::make_unique(this, + method, + host, // Host + port, // Port + path, // Path + camera_width, + camera_height, + rtsp_describe, + colours, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio + ); + } + else { + Error("Unexpected remote camera protocol '%s'", protocol.c_str()); + } + break; + } + case FILE: { + camera = zm::make_unique(this, + path.c_str(), + camera_width, + camera_height, + colours, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio + ); + break; + } + case FFMPEG: { + camera = zm::make_unique(this, + path, + second_path, + method, + options, + camera_width, + camera_height, + colours, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio, + decoder_hwaccel_name, + decoder_hwaccel_device + ); + break; + } + case NVSOCKET: { + camera = zm::make_unique(this, + host.c_str(), + port.c_str(), + path.c_str(), + camera_width, + camera_height, + colours, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio + ); + break; + } + case LIBVLC: { +#if HAVE_LIBVLC + camera = zm::make_unique(this, + path.c_str(), + method, + options, + camera_width, + camera_height, + colours, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio + ); +#else // HAVE_LIBVLC + Error("You must have vlc libraries installed to use vlc cameras for monitor %d", id); +#endif // HAVE_LIBVLC + break; + } + case LIBCURL: { +#if HAVE_LIBCURL + camera = zm::make_unique(this, + path.c_str(), + user.c_str(), + pass.c_str(), + camera_width, + camera_height, + colours, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio + ); +#else // HAVE_LIBCURL + Error("You must have libcurl installed to use ffmpeg cameras for monitor %d", id); +#endif // HAVE_LIBCURL + break; + } + case VNC: { +#if HAVE_LIBVNC + camera = zm::make_unique(this, + host.c_str(), + port.c_str(), + user.c_str(), + pass.c_str(), + width, + height, + colours, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio + ); +#else // HAVE_LIBVNC + Fatal("You must have libvnc installed to use VNC cameras for monitor id %d", id); +#endif // HAVE_LIBVNC + break; + } + default: { + Fatal("Tried to load unsupported camera type %d for monitor %u", int(type), id); + break; + } + } +} +std::shared_ptr Monitor::Load(unsigned int p_id, bool load_zones, Purpose purpose) { + std::string sql = load_monitor_sql + stringtf(" WHERE Id=%d", p_id); + + zmDbRow dbrow; + if (!dbrow.fetch(sql)) { + Error("Can't use query result: %s", mysql_error(&dbconn)); + return nullptr; + } + + std::shared_ptr monitor = std::make_shared(); + monitor->Load(dbrow.mysql_row(), load_zones, purpose); + + return monitor; +} + +bool Monitor::connect() { + if (mem_ptr != nullptr) { + Warning("Already connected. Please call disconnect first."); + } + Debug(3, "Connecting to monitor. Purpose is %d", purpose); +#if ZM_MEM_MAPPED + 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 { + 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: %s", mem_file.c_str(), strerror(errno)); + return false; + } else { + 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.c_str(), strerror(errno)); + close(map_fd); + map_fd = -1; + return false; + } + + if (map_stat.st_size != mem_size) { + if (purpose == CAPTURE) { + // Allocate the size + if (ftruncate(map_fd, mem_size) < 0) { + Error("Can't extend memory map file %s to %jd bytes: %s", mem_file.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); + close(map_fd); + map_fd = -1; + return false; + } else { + 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 (%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 (%jd bytes) to unlocked memory", mem_file.c_str(), static_cast(mem_size)); +#ifdef MAP_LOCKED + } else { + 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 (%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; + return false; + } +#else // ZM_MEM_MAPPED + shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, IPC_CREAT|0700); + if (shm_id < 0) { + Fatal("Can't shmget, probably not enough shared memory space free: %s", strerror(errno)); + } + mem_ptr = (unsigned char *)shmat(shm_id, 0, 0); + if ((int)mem_ptr == -1) { + Fatal("Can't shmat: %s", strerror(errno)); + } +#endif // ZM_MEM_MAPPED + + shared_data = (SharedData *)mem_ptr; + trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData)); + video_store_data = (VideoStoreData *)((char *)trigger_data + sizeof(TriggerData)); + shared_timestamps = (struct timeval *)((char *)video_store_data + sizeof(VideoStoreData)); + shared_images = (unsigned char *)((char *)shared_timestamps + (image_buffer_count*sizeof(struct timeval))); + + if (((unsigned long)shared_images % 64) != 0) { + /* Align images buffer to nearest 64 byte boundary */ + Debug(3, "Aligning shared memory images to the next 64 byte boundary"); + shared_images = (uint8_t*)((unsigned long)shared_images + (64 - ((unsigned long)shared_images % 64))); + } + if (!camera) LoadCamera(); + + 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 */ + } + alarm_image.AssignDirect(width, height, camera->Colours(), camera->SubpixelOrder(), + &(shared_images[image_buffer_count*camera->ImageSize()]), + camera->ImageSize(), + ZM_BUFTYPE_DONTFREE + ); + alarm_image.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) { + curl_global_init(CURL_GLOBAL_DEFAULT); //May not be the appropriate place. Need to do this before any other curl calls, and any other threads start. memset(mem_ptr, 0, mem_size); shared_data->size = sizeof(SharedData); shared_data->active = enabled; shared_data->signal = false; - shared_data->state = IDLE; + shared_data->capture_fps = 0.0; + shared_data->analysis_fps = 0.0; + 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; - shared_data->last_event = 0; + shared_data->last_event_id = 0; shared_data->action = (Action)0; shared_data->brightness = -1; shared_data->hue = -1; @@ -474,468 +897,302 @@ Monitor::Monitor( shared_data->format = camera->SubpixelOrder(); shared_data->imagesize = camera->ImageSize(); shared_data->alarm_cause[0] = 0; + shared_data->video_fifo_path[0] = 0; + shared_data->audio_fifo_path[0] = 0; + shared_data->last_frame_score = 0; + shared_data->audio_frequency = -1; + shared_data->audio_channels = -1; trigger_data->size = sizeof(TriggerData); - trigger_data->trigger_state = TRIGGER_CANCEL; + trigger_data->trigger_state = TriggerState::TRIGGER_CANCEL; trigger_data->trigger_score = 0; trigger_data->trigger_cause[0] = 0; trigger_data->trigger_text[0] = 0; trigger_data->trigger_showtext[0] = 0; - shared_data->valid = true; - video_store_data->recording = (struct timeval){0}; + video_store_data->recording = {}; + // Uh, why nothing? Why not nullptr? snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "nothing"); video_store_data->size = sizeof(VideoStoreData); - //video_store_data->frameNumber = 0; - } else if ( purpose == ANALYSIS ) { - if ( ! ( this->connect() && mem_ptr ) ) exit(-1); - shared_data->state = IDLE; - shared_data->last_read_time = 0; - shared_data->alarm_x = -1; - shared_data->alarm_y = -1; - } + usedsubpixorder = camera->SubpixelOrder(); // Used in CheckSignal + shared_data->valid = true; - if ( ( ! mem_ptr ) || ! shared_data->valid ) { - if ( purpose != QUERY ) { - Error("Shared data not initialised by capture daemon for monitor %s", name); - exit(-1); - } - } - start_time = last_fps_time = time( 0 ); - - event = 0; - - Debug(1, "Monitor %s has function %d,\n" - "label format = '%s', label X = %d, label Y = %d, label size = %d,\n" - "image buffer count = %d, warmup count = %d, pre-event count = %d, post-event count = %d, alarm frame count = %d,\n" - "fps report interval = %d, ref blend percentage = %d, alarm ref blend percentage = %d, track motion = %d", - name, function, - label_format, label_coord.X(), label_coord.Y(), label_size, - image_buffer_count, warmup_count, pre_event_count, post_event_count, alarm_frame_count, - fps_report_interval, ref_blend_perc, alarm_ref_blend_perc, track_motion ); - - //Set video recording flag for event start constructor and easy reference in code - videoRecording = ((GetOptVideoWriter() == H264PASSTHROUGH) && camera->SupportsNativeVideo()); - - n_linked_monitors = 0; - linked_monitors = 0; - - if ( purpose == ANALYSIS ) { - while( - ( shared_data->last_write_index == (unsigned int)image_buffer_count ) - && - ( shared_data->last_write_time == 0 ) - && - ( !zm_terminate ) - ) { - Debug(1, "Waiting for capture daemon last_write_index(%d), last_write_time(%d)", - shared_data->last_write_index, shared_data->last_write_time ); - sleep(1); - } - ref_image.Assign( width, height, camera->Colours(), camera->SubpixelOrder(), - image_buffer[shared_data->last_write_index].image->Buffer(), camera->ImageSize()); - adaptive_skip = true; - - ReloadLinkedMonitors(p_linked_monitors); - - if ( config.record_diag_images ) { - diag_path_r = stringtf(config.record_diag_images_fifo ? "%s/%d/diagpipe-r.jpg" : "%s/%d/diag-r.jpg", storage->Path(), id); - diag_path_d = stringtf(config.record_diag_images_fifo ? "%s/%d/diagpipe-d.jpg" : "%s/%d/diag-d.jpg", storage->Path(), id); - if (config.record_diag_images_fifo){ - FifoStream::fifo_create_if_missing(diag_path_r.c_str()); - FifoStream::fifo_create_if_missing(diag_path_d.c_str()); + //ONVIF and Amcrest Setup + //For now, only support one event type per camera, so share some state. + Poll_Trigger_State = FALSE; + if (onvif_event_listener) { // + Debug(1, "Starting ONVIF"); + Event_Poller_Healthy = FALSE; + if (onvif_options.find("closes_event") != std::string::npos) { //Option to indicate that ONVIF will send a close event message + Event_Poller_Closes_Event = TRUE; } + if (use_Amcrest_API) { + Amcrest_Manager = new AmcrestAPI(this); + } else { //using GSOAP +#ifdef WITH_GSOAP + tev__PullMessages.Timeout = "PT600S"; + tev__PullMessages.MessageLimit = 100; + soap = soap_new(); + soap->connect_timeout = 5; + soap->recv_timeout = 5; + soap->send_timeout = 5; + soap_register_plugin(soap, soap_wsse); + proxyEvent = PullPointSubscriptionBindingProxy(soap); + std::string full_url = onvif_url + "/Events"; + proxyEvent.soap_endpoint = full_url.c_str(); + + set_credentials(soap); + Debug(1, "ONVIF Endpoint: %s", proxyEvent.soap_endpoint); + if (proxyEvent.CreatePullPointSubscription(&request, response) != SOAP_OK) { + Error("Couldn't create subscription! %s, %s", soap_fault_string(soap), soap_fault_detail(soap)); + } else { + //Empty the stored messages + set_credentials(soap); + if ((proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse) != SOAP_OK) && + ( soap->error != SOAP_EOF)) { //SOAP_EOF could indicate no messages to pull. + Error("Couldn't do initial event pull! Error %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap)); + } else { + Debug(1, "Good Initial ONVIF Pull"); + Event_Poller_Healthy = TRUE; + } + } +#else + Error("zmc not compiled with GSOAP. ONVIF support not built in!"); +#endif + } + } else { + Debug(1, "Not Starting ONVIF"); + } + //End ONVIF Setup + + if (janus_enabled) { + Janus_Manager = new JanusManager(this); } - } // end if purpose == ANALYSIS -} // Monitor::Monitor + } else if (!shared_data->valid) { + Error("Shared data not initialised by capture daemon for monitor %s", name.c_str()); + return false; + } -bool Monitor::connect() { - Debug(3, "Connecting to monitor. Purpose is %d", purpose ); + // We set these here because otherwise the first fps calc is meaningless + last_fps_time = std::chrono::system_clock::now(); + last_analysis_fps_time = std::chrono::system_clock::now(); + + Debug(3, "Success connecting"); + return true; +} // Monitor::connect + +bool Monitor::disconnect() { + if (mem_ptr == nullptr) { + Debug(1, "Already disconnected"); + return true; + } + + if (purpose == CAPTURE) { + alarm_image.HoldBuffer(false); /* Allow to reset buffer */ + 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 - snprintf(mem_file, sizeof(mem_file), "%s/zm.mmap.%d", staticConfig.PATH_MAP.c_str(), id); - map_fd = open(mem_file, O_RDWR|O_CREAT, (mode_t)0600); - if ( map_fd < 0 ) { - Fatal("Can't open memory map file %s, probably not enough space free: %s", mem_file, strerror(errno)); - } else { - Debug(3, "Success opening mmap file at (%s)", mem_file); - } + msync(mem_ptr, mem_size, MS_ASYNC); + munmap(mem_ptr, mem_size); + if (map_fd >= 0) close(map_fd); - struct stat map_stat; - if ( fstat(map_fd, &map_stat) < 0 ) - Fatal("Can't stat memory map file %s: %s, is the zmc process for this monitor running?", mem_file, strerror(errno)); + map_fd = -1; + mem_ptr = nullptr; + shared_data = nullptr; - if ( map_stat.st_size != mem_size ) { - if ( purpose == CAPTURE ) { - // Allocate the size - if ( ftruncate(map_fd, mem_size) < 0 ) { - Fatal("Can't extend memory map file %s to %d bytes: %s", mem_file, mem_size, strerror(errno)); - } - } else if ( map_stat.st_size == 0 ) { - Error("Got empty memory map file size %ld, is the zmc process for this monitor running?", map_stat.st_size, mem_size); - close(map_fd); - map_fd = -1; - return false; - } else { - Error("Got unexpected memory map file size %ld, expected %d", map_stat.st_size, mem_size); - close(map_fd); - map_fd = -1; - return false; - } - } - - Debug(3, "MMap file size is %ld", map_stat.st_size); -#ifdef MAP_LOCKED - mem_ptr = (unsigned char *)mmap(NULL, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_LOCKED, map_fd, 0); - if ( mem_ptr == MAP_FAILED ) { - if ( errno == EAGAIN ) { - Debug(1, "Unable to map file %s (%d bytes) to locked memory, trying unlocked", mem_file, mem_size); -#endif - mem_ptr = (unsigned char *)mmap(NULL, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0); - Debug(1, "Mapped file %s (%d bytes) to unlocked memory", mem_file, mem_size); -#ifdef MAP_LOCKED - } else { - Error("Unable to map file %s (%d bytes) to locked memory (%s)", mem_file, mem_size, strerror(errno)); - } - } -#endif - if ( mem_ptr == MAP_FAILED ) - Fatal("Can't map file %s (%d bytes) to memory: %s(%d)", mem_file, mem_size, strerror(errno), errno); - if ( mem_ptr == NULL ) { - Error("mmap gave a null address:"); - } else { - Debug(3, "mmapped to %p", mem_ptr); - } #else // ZM_MEM_MAPPED - shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, IPC_CREAT|0700); - if ( shm_id < 0 ) { - Fatal("Can't shmget, probably not enough shared memory space free: %s", strerror(errno)); + struct shmid_ds shm_data; + if (shmctl(shm_id, IPC_STAT, &shm_data) < 0) { + Debug(3, "Can't shmctl: %s", strerror(errno)); + return false; } - mem_ptr = (unsigned char *)shmat(shm_id, 0, 0); - if ( mem_ptr < (void *)0 ) { - Fatal("Can't shmat: %s", strerror(errno)); + + shm_id = 0; + + if ((shm_data.shm_nattch <= 1) and (shmctl(shm_id, IPC_RMID, 0) < 0)) { + Debug(3, "Can't shmctl: %s", strerror(errno)); + return false; + } + + if (shmdt(mem_ptr) < 0) { + Debug(3, "Can't shmdt: %s", strerror(errno)); + return false; } #endif // ZM_MEM_MAPPED - shared_data = (SharedData *)mem_ptr; - trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData)); - video_store_data = (VideoStoreData *)((char *)trigger_data + sizeof(TriggerData)); - struct timeval *shared_timestamps = (struct timeval *)((char *)video_store_data + sizeof(VideoStoreData)); - unsigned char *shared_images = (unsigned char *)((char *)shared_timestamps + (image_buffer_count*sizeof(struct timeval))); - if ( ((unsigned long)shared_images % 64) != 0 ) { - /* Align images buffer to nearest 64 byte boundary */ - Debug(3,"Aligning shared memory images to the next 64 byte boundary"); - shared_images = (uint8_t*)((unsigned long)shared_images + (64 - ((unsigned long)shared_images % 64))); + 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; } - Debug(3, "Allocating %d image buffers", image_buffer_count); - image_buffer = new Snapshot[image_buffer_count]; - for ( int i = 0; i < image_buffer_count; i++ ) { - image_buffer[i].timestamp = &(shared_timestamps[i]); - image_buffer[i].image = new Image( width, height, camera->Colours(), camera->SubpixelOrder(), &(shared_images[i*camera->ImageSize()]) ); - image_buffer[i].image->HoldBuffer(true); /* Don't release the internal buffer or replace it with another */ - } - if ( (deinterlacing & 0xff) == 4) { - /* Four field motion adaptive deinterlacing in use */ - /* Allocate a buffer for the next image */ - next_buffer.image = new Image( width, height, camera->Colours(), camera->SubpixelOrder()); - next_buffer.timestamp = new struct timeval; - } - if ( purpose == ANALYSIS ) { - if ( analysis_fps ) { - // Size of pre event buffer must be greater than pre_event_count - // if alarm_frame_count > 1, because in this case the buffer contains - // alarmed images that must be discarded when event is created - pre_event_buffer_count = pre_event_count + alarm_frame_count - 1; - pre_event_buffer = new Snapshot[pre_event_buffer_count]; - for ( int i = 0; i < pre_event_buffer_count; i++ ) { - pre_event_buffer[i].timestamp = new struct timeval; - *pre_event_buffer[i].timestamp = {0,0}; - pre_event_buffer[i].image = new Image( width, height, camera->Colours(), camera->SubpixelOrder()); - } - } // end if max_analysis_fps - timestamps = new struct timeval *[pre_event_count]; - images = new Image *[pre_event_count]; - last_signal = shared_data->signal; - } // end if purpose == ANALYSIS -Debug(3, "Success connecting"); return true; -} // end Monitor::connect +} // end bool Monitor::disconnect() Monitor::~Monitor() { - if ( n_linked_monitors ) { - for( int i = 0; i < n_linked_monitors; i++ ) { + Close(); + + if (mem_ptr != nullptr) { + if (purpose != QUERY) { + memset(mem_ptr, 0, mem_size); + } // end if purpose != query + disconnect(); + } // end if mem_ptr + + // Will be free by packetqueue destructor + analysis_it = nullptr; + decoder_it = nullptr; + + delete storage; + if (n_linked_monitors) { + for ( int i=0; i < n_linked_monitors; i++ ) { delete linked_monitors[i]; } delete[] linked_monitors; - linked_monitors = 0; + linked_monitors = nullptr; } - if ( timestamps ) { - delete[] timestamps; - timestamps = 0; + + if (video_fifo) delete video_fifo; + if (audio_fifo) delete audio_fifo; + if (dest_frame) av_frame_free(&dest_frame); + if (convert_context) { + sws_freeContext(convert_context); + convert_context = nullptr; } - if ( images ) { - delete[] images; - images = 0; + if (Amcrest_Manager != nullptr) { + delete Amcrest_Manager; } - if ( privacy_bitmask ) { + if (purpose == CAPTURE) { + curl_global_cleanup(); //not sure about this location. + } +} // end Monitor::~Monitor() + +void Monitor::AddPrivacyBitmask() { + if (privacy_bitmask) { delete[] privacy_bitmask; - privacy_bitmask = NULL; + privacy_bitmask = nullptr; } - if ( mem_ptr ) { - if ( event ) { - Info( "%s: image_count:%d - Closing event %" PRIu64 ", shutting down", name, image_count, event->Id() ); - closeEvent(); + Image *privacy_image = nullptr; - // closeEvent may start another thread to close the event, so wait for it to finish - if ( event_delete_thread ) { - event_delete_thread->join(); - delete event_delete_thread; - event_delete_thread = NULL; - } - } - - if ( (deinterlacing & 0xff) == 4) { - delete next_buffer.image; - delete next_buffer.timestamp; - } - for ( int i = 0; i < image_buffer_count; i++ ) { - delete image_buffer[i].image; - } - delete[] image_buffer; - } // end if mem_ptr - - for ( int i = 0; i < n_zones; i++ ) { - delete zones[i]; - } - delete[] zones; - - delete camera; - delete storage; - - if ( mem_ptr ) { - if ( purpose == ANALYSIS ) { - shared_data->state = state = IDLE; - shared_data->last_read_index = image_buffer_count; - shared_data->last_read_time = 0; - - if ( analysis_fps ) { - for ( int i = 0; i < pre_event_buffer_count; i++ ) { - delete pre_event_buffer[i].image; - delete pre_event_buffer[i].timestamp; - } - delete[] pre_event_buffer; - } - } else if ( purpose == CAPTURE ) { - shared_data->valid = false; - memset( mem_ptr, 0, mem_size ); - } - -#if ZM_MEM_MAPPED - if ( msync(mem_ptr, mem_size, MS_SYNC) < 0 ) - Error("Can't msync: %s", strerror(errno)); - if ( munmap(mem_ptr, mem_size) < 0 ) - Fatal("Can't munmap: %s", strerror(errno)); - close( map_fd ); - - if ( purpose == CAPTURE ) { - // How about we store this in the object on instantiation so that we don't have to do this again. - char mmap_path[PATH_MAX] = ""; - snprintf(mmap_path, sizeof(mmap_path), "%s/zm.mmap.%d", staticConfig.PATH_MAP.c_str(), id); - - if ( unlink(mmap_path) < 0 ) { - Warning("Can't unlink '%s': %s", mmap_path, strerror(errno)); - } - } -#else // ZM_MEM_MAPPED - struct shmid_ds shm_data; - if ( shmctl(shm_id, IPC_STAT, &shm_data) < 0 ) { - Fatal("Can't shmctl: %s", strerror(errno)); - } - if ( shm_data.shm_nattch <= 1 ) { - if ( shmctl(shm_id, IPC_RMID, 0) < 0 ) { - Fatal("Can't shmctl: %s", strerror(errno)); - } - } -#endif // ZM_MEM_MAPPED - } // end if mem_ptr -} - -void Monitor::AddZones( int p_n_zones, Zone *p_zones[] ) { - for ( int i = 0; i < n_zones; i++ ) - delete zones[i]; - delete[] zones; - n_zones = p_n_zones; - zones = p_zones; -} - -void Monitor::AddPrivacyBitmask( Zone *p_zones[] ) { - if ( privacy_bitmask ) { - delete[] privacy_bitmask; - privacy_bitmask = NULL; - } - Image *privacy_image = NULL; - - for ( int i = 0; i < n_zones; i++ ) { - if ( p_zones[i]->IsPrivacy() ) { - if ( !privacy_image ) { - privacy_image = new Image( width, height, 1, ZM_SUBPIX_ORDER_NONE); + for (const Zone &zone : zones) { + //for (int i=0; i < zones.size(); i++) { + if (zone.IsPrivacy()) { + if (!privacy_image) { + privacy_image = new Image(width, height, 1, ZM_SUBPIX_ORDER_NONE); privacy_image->Clear(); } - privacy_image->Fill( 0xff, p_zones[i]->GetPolygon() ); - privacy_image->Outline( 0xff, p_zones[i]->GetPolygon() ); + privacy_image->Fill(0xff, zone.GetPolygon()); + privacy_image->Outline(0xff, zone.GetPolygon()); } } // end foreach zone - if ( privacy_image ) + if (privacy_image) privacy_bitmask = privacy_image->Buffer(); } -Monitor::State Monitor::GetState() const { - return (State)shared_data->state; +Image *Monitor::GetAlarmImage() { + return &alarm_image; } -int Monitor::GetImage( int index, int scale ) { - if ( index < 0 || index > image_buffer_count ) { +int Monitor::GetImage(int32_t index, int scale) { + 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 (!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 ( index != image_buffer_count ) { - Image *image; - // If we are going to be modifying the snapshot before writing, then we need to copy it - if ( ( scale != ZM_SCALE_BASE ) || ( !config.timestamp_on_capture ) ) { - Snapshot *snap = &image_buffer[index]; - Image *snap_image = snap->image; + std::string filename = stringtf("Monitor%u.jpg", id); + // 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)) { + Image image; + image.Assign(*image_buffer[index]); - alarm_image.Assign(*snap_image); - - - //write_image.Assign( *snap_image ); - - if ( scale != ZM_SCALE_BASE ) { - alarm_image.Scale(scale); - } - - if ( !config.timestamp_on_capture ) { - TimestampImage(&alarm_image, snap->timestamp); - } - image = &alarm_image; - } else { - image = image_buffer[index].image; + if (scale != ZM_SCALE_BASE) { + 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(&image, SystemTimePoint(zm::chrono::duration_cast(shared_timestamps[index]))); + } + return image.WriteJpeg(filename); + } else { + return image_buffer[index]->WriteJpeg(filename); + } +} + +ZMPacket *Monitor::getSnapshot(int index) const { + if ((index < 0) || (index >= image_buffer_count)) { + index = shared_data->last_write_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 0; + return nullptr; } -struct timeval Monitor::GetTimestamp( int index ) const { - if ( index < 0 || index > image_buffer_count ) { - index = shared_data->last_write_index; - } +SystemTimePoint Monitor::GetTimestamp(int index) const { + ZMPacket *packet = getSnapshot(index); + if (packet) + return packet->timestamp; - if ( index != image_buffer_count ) { - Snapshot *snap = &image_buffer[index]; - - return *(snap->timestamp); - } else { - static struct timeval null_tv = { 0, 0 }; - - return null_tv; - } + return {}; } unsigned int Monitor::GetLastReadIndex() const { - return( shared_data->last_read_index!=(unsigned int)image_buffer_count?shared_data->last_read_index:-1 ); + return ( shared_data->last_read_index != image_buffer_count ? shared_data->last_read_index : -1 ); } unsigned int Monitor::GetLastWriteIndex() const { - return( shared_data->last_write_index!=(unsigned int)image_buffer_count?shared_data->last_write_index:-1 ); + return ( shared_data->last_write_index != image_buffer_count ? shared_data->last_write_index : -1 ); } uint64_t Monitor::GetLastEventId() const { -#if 0 - Debug(2, "mem_ptr(%x), State(%d) last_read_index(%d) last_read_time(%d) last_event(%" PRIu64 ")", - mem_ptr, - shared_data->state, - shared_data->last_read_index, - shared_data->last_read_time, - shared_data->last_event - ); -#endif - return shared_data->last_event; + return shared_data->last_event_id; } // This function is crap. double Monitor::GetFPS() const { - // last_write_index is the last capture index. It starts as == image_buffer_count so that the first asignment % image_buffer_count = 0; - int index1 = shared_data->last_write_index; - if ( index1 == image_buffer_count ) { - // last_write_index only has this value on startup before capturing anything. - return 0.0; - } - Snapshot *snap1 = &image_buffer[index1]; - if ( !snap1->timestamp || !snap1->timestamp->tv_sec ) { - // This should be impossible - Warning("Impossible situation. No timestamp on captured image index was %d, image-buffer_count was (%d)", index1, image_buffer_count); - return 0.0; - } - struct timeval time1 = *snap1->timestamp; - - int image_count = image_buffer_count; - int index2 = (index1+1)%image_buffer_count; - Snapshot *snap2 = &image_buffer[index2]; - // the timestamp pointers are initialized on connection, so that's redundant - // tv_sec is probably only zero during the first loop of capturing, so this basically just counts the unused images. - // The problem is that there is no locking, and we set the timestamp before we set last_write_index, - // so there is a small window where the next image can have a timestamp in the future - while ( !snap2->timestamp || !snap2->timestamp->tv_sec || tvDiffSec(*snap2->timestamp, *snap1->timestamp) < 0 ) { - if ( index1 == index2 ) { - // All images are uncaptured - return 0.0; - } - index2 = (index2+1)%image_buffer_count; - snap2 = &image_buffer[index2]; - 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 = 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; + return get_capture_fps(); } +/* I think this returns the # of micro seconds that we should sleep in order to maintain the desired analysis rate */ useconds_t Monitor::GetAnalysisRate() { - double capturing_fps = GetFPS(); - if ( !analysis_fps ) { + double capture_fps = get_capture_fps(); + if ( !analysis_fps_limit ) { return 0; - } else if ( analysis_fps > capturing_fps ) { - Warning("Analysis fps (%.2f) is greater than capturing fps (%.2f)", analysis_fps, capturing_fps); + } else if ( analysis_fps_limit > capture_fps ) { + if ( last_fps_time != last_analysis_fps_time ) { + // At startup they are equal, should never be equal again + Warning("Analysis fps (%.2f) is greater than capturing fps (%.2f)", analysis_fps_limit, capture_fps); + } return 0; - } else { - return ( ( 1000000 / analysis_fps ) - ( 1000000 / capturing_fps ) ); + } else if ( capture_fps ) { + return( ( 1000000 / analysis_fps_limit ) - ( 1000000 / capture_fps ) ); } + return 0; } void Monitor::UpdateAdaptiveSkip() { if ( config.opt_adaptive_skip ) { - double capturing_fps = GetFPS(); + double capturing_fps = get_capture_fps(); + double analysis_fps = get_analysis_fps(); if ( adaptive_skip && analysis_fps && ( analysis_fps < capturing_fps ) ) { Info("Analysis fps (%.2f) is lower than capturing fps (%.2f), disabling adaptive skip feature", analysis_fps, capturing_fps); adaptive_skip = false; @@ -949,18 +1206,18 @@ void Monitor::UpdateAdaptiveSkip() { } void Monitor::ForceAlarmOn( int force_score, const char *force_cause, const char *force_text ) { - trigger_data->trigger_state = TRIGGER_ON; + trigger_data->trigger_state = TriggerState::TRIGGER_ON; trigger_data->trigger_score = force_score; strncpy(trigger_data->trigger_cause, force_cause, sizeof(trigger_data->trigger_cause)-1); strncpy(trigger_data->trigger_text, force_text, sizeof(trigger_data->trigger_text)-1); } void Monitor::ForceAlarmOff() { - trigger_data->trigger_state = TRIGGER_OFF; + trigger_data->trigger_state = TriggerState::TRIGGER_OFF; } void Monitor::CancelForced() { - trigger_data->trigger_state = TRIGGER_CANCEL; + trigger_data->trigger_state = TriggerState::TRIGGER_CANCEL; } void Monitor::actionReload() { @@ -970,25 +1227,15 @@ void Monitor::actionReload() { void Monitor::actionEnable() { shared_data->action |= RELOAD; - db_mutex.lock(); - static char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), "UPDATE `Monitors` SET `Enabled` = 1 WHERE `Id` = %d", id); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - } - db_mutex.unlock(); + std::string sql = stringtf("UPDATE `Monitors` SET `Enabled` = 1 WHERE `Id` = %u", id); + zmDbDo(sql); } void Monitor::actionDisable() { shared_data->action |= RELOAD; - static char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), "UPDATE `Monitors` SET `Enabled` = 0 WHERE `Id` = %d", id); - db_mutex.lock(); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - } - db_mutex.unlock(); + std::string sql = stringtf("UPDATE `Monitors` SET `Enabled` = 0 WHERE `Id` = %u", id); + zmDbDo(sql); } void Monitor::actionSuspend() { @@ -1000,127 +1247,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) { @@ -1134,18 +1407,18 @@ void Monitor::DumpZoneImage(const char *zone_string) { } } - Image *zone_image = NULL; + Image *zone_image = nullptr; if ( ( (!staticConfig.SERVER_ID) || ( staticConfig.SERVER_ID == server_id ) ) && mem_ptr ) { Debug(3, "Trying to load from local zmc"); int index = shared_data->last_write_index; - Snapshot *snap = &image_buffer[index]; + ZMPacket *snap = getSnapshot(index); zone_image = new Image(*snap->image); } else { Debug(3, "Trying to load from event"); // 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); @@ -1153,7 +1426,7 @@ void Monitor::DumpZoneImage(const char *zone_string) { stream->setStreamStart(event_id, (unsigned int)1); zone_image = stream->getImage(); delete stream; - stream = NULL; + stream = nullptr; } else { Error("Unable to load an event for monitor %d", id); return; @@ -1164,187 +1437,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() ) { - colour = RGB_RED; - } else if ( zones[i]->IsInclusive() ) { - colour = RGB_ORANGE; - } else if ( zones[i]->IsExclusive() ) { - colour = RGB_PURPLE; - } else if ( zones[i]->IsPreclusive() ) { - colour = RGB_BLUE; + if (zone.IsActive()) { + colour = kRGBRed; + } else if (zone.IsInclusive()) { + colour = kRGBOrange; + } else if (zone.IsExclusive()) { + colour = kRGBPurple; + } else if (zone.IsPreclusive()) { + colour = kRGBBlue; } else { - colour = RGB_WHITE; + colour = kRGBWhite; } } - zone_image->Fill(colour, 2, zones[i]->GetPolygon()); - zone_image->Outline(colour, zones[i]->GetPolygon()); + zone_image->Fill(colour, 2, zone.GetPolygon()); + zone_image->Outline(colour, zone.GetPolygon()); } - if ( extra_zone.getNumCoords() ) { + 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) -bool Monitor::Analyse() { - if ( shared_data->last_read_index == shared_data->last_write_index ) { - // I wonder how often this happens. Maybe if this happens we should sleep or something? - return false; - } - - struct timeval now; - gettimeofday(&now, NULL); - - if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) { - if ( now.tv_sec != last_fps_time ) { - double new_fps = double(fps_report_interval)/(now.tv_sec - last_fps_time); - Info("%s: %d - Analysing at %.2f fps", name, image_count, new_fps); - if ( fps != new_fps ) { - fps = new_fps; - db_mutex.lock(); - static char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), "INSERT INTO Monitor_Status (MonitorId,AnalysisFPS) VALUES (%d, %.2lf) ON DUPLICATE KEY UPDATE AnalysisFPS = %.2lf", id, fps, fps); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - } - db_mutex.unlock(); - } // end if fps != new_fps - - last_fps_time = now.tv_sec; - } - } - - int index; - if ( adaptive_skip ) { - // I think the idea behind adaptive skip is if we are falling behind, then skip a bunch, but not all - int read_margin = shared_data->last_read_index - shared_data->last_write_index; - if ( read_margin < 0 ) read_margin += image_buffer_count; - - int step = 1; - // Isn't read_margin always > 0 here? - if ( read_margin > 0 ) { - // TODO explain this so... 90% of image buffer / 50% of read margin? - step = (9*image_buffer_count)/(5*read_margin); - } - - int pending_frames = shared_data->last_write_index - shared_data->last_read_index; - if ( pending_frames < 0 ) pending_frames += image_buffer_count; - - Debug(4, - "ReadIndex:%d, WriteIndex: %d, PendingFrames = %d, ReadMargin = %d, Step = %d", - shared_data->last_read_index, shared_data->last_write_index, pending_frames, read_margin, step - ); - if ( step <= pending_frames ) { - index = (shared_data->last_read_index+step)%image_buffer_count; - } else { - if ( pending_frames ) { - Warning("Approaching buffer overrun, consider slowing capture, simplifying analysis or increasing ring buffer size"); - } - index = shared_data->last_write_index%image_buffer_count; - } - } else { - index = shared_data->last_write_index%image_buffer_count; - } - - Snapshot *snap = &image_buffer[index]; - struct timeval *timestamp = snap->timestamp; - Image *snap_image = snap->image; +void Monitor::CheckAction() { + 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? @@ -1361,546 +1564,691 @@ bool Monitor::Analyse() { } 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; - } - if ( shared_data->action & RESUME ) { + } else if ( shared_data->action & RESUME ) { if ( Enabled() && !Active() ) { Info("Received resume indication at count %d", image_count); shared_data->active = true; - ref_image = *snap_image; - ready_count = image_count+(warmup_count/2); + ref_image.DumpImgBuffer(); // Will get re-assigned by analysis thread shared_data->alarm_x = shared_data->alarm_y = -1; } shared_data->action &= ~RESUME; } } // 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 = *snap_image; - ready_count = image_count+(warmup_count/2); - auto_resume_time = 0; + ref_image.DumpImgBuffer(); // Will get re-assigned by analysis thread + } +} + +void Monitor::UpdateFPS() { + if ( fps_report_interval and + ( + !(image_count%fps_report_interval) + or + ( (image_count < fps_report_interval) and !(image_count%10) ) + ) + ) { + 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 > Seconds(1)) { + // # of images per interval / the amount of time it took + 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; + + 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::UpdateFPS() + +//Thread where ONVIF polling, and other similar status polling can happen. +//Since these can be blocking, run here to avoid intefering with other processing +bool Monitor::Poll() { + + //We want to trigger every 5 seconds or so. so grab the time at the beginning of the loop, and sleep at the end. + std::chrono::system_clock::time_point loop_start_time = std::chrono::system_clock::now(); + + if (Event_Poller_Healthy) { + if(use_Amcrest_API) { + Amcrest_Manager->WaitForMessage(); + } else { + +#ifdef WITH_GSOAP + set_credentials(soap); + int result = proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse); + if (result != SOAP_OK) { + if (result != SOAP_EOF) { //Ignore the timeout error + Error("Failed to get ONVIF messages! %s", soap_fault_string(soap)); + Event_Poller_Healthy = FALSE; + } + } else { + Debug(1, "Got Good Response! %i", result); + for (auto msg : tev__PullMessagesResponse.wsnt__NotificationMessage) { + if (msg->Topic->__any.text != NULL && + std::strstr(msg->Topic->__any.text, "MotionAlarm") && + msg->Message.__any.elts != NULL && + msg->Message.__any.elts->next != NULL && + msg->Message.__any.elts->next->elts != NULL && + msg->Message.__any.elts->next->elts->atts != NULL && + msg->Message.__any.elts->next->elts->atts->next != NULL && + msg->Message.__any.elts->next->elts->atts->next->text != NULL) { + Debug(1,"Got Motion Alarm!"); + if (strcmp(msg->Message.__any.elts->next->elts->atts->next->text, "true") == 0) { + //Event Start + Debug(1,"Triggered on ONVIF"); + if (!Poll_Trigger_State) { + Debug(1,"Triggered Event"); + Poll_Trigger_State = TRUE; + std::this_thread::sleep_for (std::chrono::seconds(1)); //thread sleep + } + } else { + Debug(1, "Triggered off ONVIF"); + Poll_Trigger_State = FALSE; + if (!Event_Poller_Closes_Event) { //If we get a close event, then we know to expect them. + Event_Poller_Closes_Event = TRUE; + Debug(1,"Setting ClosesEvent"); + } + } + } + } + } +#endif + } + } + if (janus_enabled) { + + if (Janus_Manager->check_janus() == 0) { + Janus_Manager->add_to_janus(); + } + } + std::this_thread::sleep_until(loop_start_time + std::chrono::seconds(5)); + return TRUE; +} //end Poll + +// Would be nice if this JUST did analysis +// This idea is that we should be analysing as close to the capture frame as possible. +// This function should process as much as possible before returning +// +// If there is an event, the we should do our best to empty the queue. +// If there isn't then we keep pre-event + alarm frames. = pre_event_count +bool Monitor::Analyse() { + // if have event, send frames until we find a video packet, at which point do analysis. Adaptive skip should only affect which frames we do analysis on. + + // get_analysis_packet will lock the packet and may wait if analysis_it is at the end + ZMLockedPacket *packet_lock = packetqueue.get_packet(analysis_it); + if (!packet_lock) return false; + 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) { + Error("skipping because score was %d", snap->score); + packetqueue.unlock(packet_lock); + packetqueue.increment_it(analysis_it); + return false; + } + // Ready means that we have captured the warmup # of frames + if (!Ready()) { + Debug(3, "Not ready?"); + delete packet_lock; + return false; } - if ( Enabled() ) { - bool signal = shared_data->signal; - bool signal_change = (signal != last_signal); + // 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)", signal, signal_change); + 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); - if ( trigger_data->trigger_state != TRIGGER_OFF ) { - unsigned int score = 0; - if ( Ready() ) { - std::string cause; - Event::StringSetMap noteSetMap; + { // scope for event lock + // Need to guard around event creation/deletion from Reload() + std::lock_guard lck(event_mutex); - if ( trigger_data->trigger_state == TRIGGER_ON ) { - score += trigger_data->trigger_score; - Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score); - if ( !event ) { - // How could it have a length already? - //if ( cause.length() ) - //cause += ", "; - cause += trigger_data->trigger_cause; - } + // if we have been told to be OFF, then we are off and don't do any processing. + if (trigger_data->trigger_state != TriggerState::TRIGGER_OFF) { + Debug(4, "Trigger not OFF state is (%d)", int(trigger_data->trigger_state)); + + int score = 0; + std::string cause; + Event::StringSetMap noteSetMap; + +#ifdef WITH_GSOAP + if (onvif_event_listener && Event_Poller_Healthy) { + if (Poll_Trigger_State) { + score += 9; + Debug(1, "Triggered on ONVIF"); Event::StringSet noteSet; - noteSet.insert(trigger_data->trigger_text); - noteSetMap[trigger_data->trigger_cause] = noteSet; - } + noteSet.insert("ONVIF2"); + noteSetMap[MOTION_CAUSE] = noteSet; + cause += "ONVIF"; + //If the camera isn't going to send an event close, we need to close it here, but only after it has actually triggered an alarm. + if (!Event_Poller_Closes_Event && state == ALARM) + Poll_Trigger_State = FALSE; + } // end ONVIF_Trigger + } // end if (onvif_event_listener && Event_Poller_Healthy) +#endif - if ( signal_change ) { - const char *signalText; - if ( !signal ) { - signalText = "Lost"; - } else { - signalText = "Reacquired"; - score += 100; - } - Warning("%s: %s", SIGNAL_CAUSE, signalText); - if ( event && !signal ) { - Info("%s: %03d - Closing event %" PRIu64 ", signal loss", name, image_count, event->Id()); + // Specifically told to be on. Setting the score here is not enough to trigger the alarm. Must jump directly to ALARM + if (trigger_data->trigger_state == TriggerState::TRIGGER_ON) { + score += trigger_data->trigger_score; + Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score); + if (!cause.empty()) cause += ", "; + cause += trigger_data->trigger_cause; + Event::StringSet noteSet; + noteSet.insert(trigger_data->trigger_text); + noteSetMap[trigger_data->trigger_cause] = noteSet; + shared_data->state = state = ALARM; + } // end if trigger_on + + // FIXME this snap might not be the one that caused the signal change. Need to store that in the packet. + if (signal_change) { + Debug(2, "Signal change, new signal is %d", signal); + if (!signal) { + if (event) { + event->addNote(SIGNAL_CAUSE, "Lost"); + Info("%s: %03d - Closing event %" PRIu64 ", signal loss", name.c_str(), analysis_image_count, event->Id()); closeEvent(); } - if ( !event ) { - if ( cause.length() ) - cause += ", "; - cause += SIGNAL_CAUSE; + } else if (function == MOCORD or function == RECORD) { + if (!event) { + if (!cause.empty()) cause += ", "; + cause += SIGNAL_CAUSE + std::string(": Reacquired"); + } else { + event->addNote(SIGNAL_CAUSE, "Reacquired"); } - Event::StringSet noteSet; - noteSet.insert(signalText); - noteSetMap[SIGNAL_CAUSE] = noteSet; - shared_data->state = state = IDLE; - shared_data->active = signal; - ref_image = *snap_image; - - } else if ( signal ) { - if ( Active() && (function == MODECT || function == MOCORD) ) { - // All is good, so add motion detection score. - Event::StringSet zoneSet; - if ( (!motion_frame_skip) || !(image_count % (motion_frame_skip+1)) ) { - // Get new score. - int new_motion_score = DetectMotion(*snap_image, zoneSet); - - Debug(3, - "After motion detection, last_motion_score(%d), new motion score(%d)", - last_motion_score, new_motion_score - ); - last_motion_score = new_motion_score; - } - if ( last_motion_score ) { - score += last_motion_score; - if ( !event ) { - if ( cause.length() ) - cause += ", "; - cause += MOTION_CAUSE; - } - noteSetMap[MOTION_CAUSE] = zoneSet; - } // end if motion_score - //shared_data->active = signal; // unneccessary active gets set on signal change - } // end if active and doing motion detection + if (0 and snap->in_frame && ( + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) + || + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) + ) ) { + Debug(1, "assigning refimage from v-channel"); + Image v_image(snap->in_frame->width, + snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[0], 0); + ref_image.Assign(v_image); + } else if (snap->image) { + Debug(1, "assigning refimage from snap->image"); + ref_image.Assign(*(snap->image)); + } + } + shared_data->state = state = IDLE; + shared_data->active = signal; + } // 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 ) { + 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() ) { - if ( linked_monitors[i]->hasAlarmed() ) { - if ( !event ) { - if ( first_link ) { - if ( cause.length() ) + if (linked_monitors[i]->isConnected()) { + Debug(1, "Linked monitor %d %s is connected", + linked_monitors[i]->Id(), linked_monitors[i]->Name()); + if (linked_monitors[i]->hasAlarmed()) { + Debug(1, "Linked monitor %d %s is alarmed", + linked_monitors[i]->Id(), linked_monitors[i]->Name()); + if (!event) { + if (first_link) { + if (cause.length()) cause += ", "; cause += LINKED_CAUSE; first_link = false; } } noteSet.insert(linked_monitors[i]->Name()); - score += 50; + score += linked_monitors[i]->lastFrameScore(); // 50; + } else { + Debug(1, "Linked monitor %d %s is not alarmed", + linked_monitors[i]->Id(), linked_monitors[i]->Name()); } } else { + Debug(1, "Linked monitor %d %d is not connected. Connecting.", i, linked_monitors[i]->Id()); linked_monitors[i]->connect(); } - } // end foreach linked_monit - if ( noteSet.size() > 0 ) + } // end foreach linked_monitor + if (noteSet.size() > 0) noteSetMap[LINKED_CAUSE] = noteSet; - } // end if linked_monitors + } // end if linked_monitors - //TODO: What happens is the event closes and sets recording to false then recording to true again so quickly that our capture daemon never picks it up. Maybe need a refresh flag? - if ( function == RECORD || function == MOCORD ) { - if ( event ) { - Debug(3, "Have signal and recording with open event at (%d.%d)", timestamp->tv_sec, timestamp->tv_usec); - - if ( section_length - && ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= section_length ) - && ( (event_close_mode != CLOSE_TIME) || ! ( timestamp->tv_sec % section_length ) ) - ) { - Info("%s: %03d - Closing event %" PRIu64 ", section end forced %d - %d = %d >= %d", - name, image_count, event->Id(), - timestamp->tv_sec, video_store_data->recording.tv_sec, - timestamp->tv_sec - video_store_data->recording.tv_sec, - section_length - ); - closeEvent(); - } // end if section_length - } // end if event - - if ( !event ) { - - // Create event - event = new Event(this, *timestamp, "Continuous", noteSetMap, videoRecording); - shared_data->last_event = event->Id(); - //set up video store data - snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); - video_store_data->recording = event->StartTime(); - - Info("%s: %03d - Opening new event %" PRIu64 ", section start", name, image_count, event->Id()); - - /* To prevent cancelling out an existing alert\prealarm\alarm state */ - if ( state == IDLE ) { - shared_data->state = state = TAPE; - } - - } // end if ! event - } // end if function == RECORD || function == MOCORD) - } // end if !signal_change && signal - - if ( score ) { - if ( (state == IDLE) || (state == TAPE) || (state == PREALARM) ) { - // If we should end then previous continuous event and start a new non-continuous event - if ( event && event->Frames() - && (!event->AlarmFrames()) - && (event_close_mode == CLOSE_ALARM) - && ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= min_section_length ) - ) { - Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins", - name, image_count, event->Id()); - closeEvent(); - } else if ( event ) { - // This is so if we need more than 1 alarm frame before going into alarm, so it is basically if we have enough alarm frames - Debug(3, "pre-alarm-count in event %d, event frames %d, alarm frames %d event length %d >=? %d", - Event::PreAlarmCount(), event->Frames(), event->AlarmFrames(), - ( timestamp->tv_sec - video_store_data->recording.tv_sec ), min_section_length - ); + /* 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. + // decoder thread might be waiting on the lock for this packet. + // So we need to relinquish the lock and wait. Waiting automatically relinquishes the lock + // So... + Debug(1, "Waiting for decode"); + packet_lock->wait(); + //packetqueue.unlock(packet_lock); // This will delete packet_lock and notify_all + //packetqueue.wait(); + ////packet_lock->lock(); + } // end while ! decoded + if (zm_terminate or analysis_thread->Stopped()) { + delete packet_lock; + return false; } - if ( (!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1) ) { - shared_data->state = state = ALARM; - // 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()); - } - } - 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); + } // end if decoding enabled - if ( !event ) { - int pre_index; - int pre_event_images = pre_event_count; + if (Active() and (function == MODECT or function == MOCORD)) { + Debug(3, "signal and active and modect"); + Event::StringSet zoneSet; - if ( analysis_fps && pre_event_count ) { - // If analysis fps is set, - // compute the index for pre event images in the dedicated buffer - pre_index = pre_event_buffer_count ? image_count % pre_event_buffer_count : 0; - Debug(3, "pre-index %d = image_count(%d) %% pre_event_buffer_count(%d)", - pre_index, image_count, pre_event_buffer_count); + 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); + } - // Seek forward the next filled slot in to the buffer (oldest data) - // from the current position - while ( pre_event_images && !pre_event_buffer[pre_index].timestamp->tv_sec ) { - pre_index = (pre_index + 1)%pre_event_buffer_count; - // Slot is empty, removing image from counter - pre_event_images--; - } - Debug(3, "pre-index %d, pre-event_images %d", - pre_index, pre_event_images); - - event = new Event(this, *(pre_event_buffer[pre_index].timestamp), cause, noteSetMap); + if (snap->image) { + // decoder may not have been able to provide an image + if (!ref_image.Buffer()) { + Debug(1, "Assigning instead of Detecting"); + if (0 and snap->in_frame && ( + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) + || + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) + ) ) { + Debug(1, "assigning refimage from v-channel"); + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[0], 0); + ref_image.Assign(v_image); } else { - // If analysis fps is not set (analysis performed at capturing framerate), - // compute the index for pre event images in the capturing buffer - if ( alarm_frame_count > 1 ) - pre_index = ((index + image_buffer_count) - ((alarm_frame_count - 1) + pre_event_count))%image_buffer_count; - else - pre_index = ((index + image_buffer_count) - pre_event_count)%image_buffer_count; - - Debug(3, "Resulting pre_index(%d) from index(%d) + image_buffer_count(%d) - pre_event_count(%d)", - pre_index, index, image_buffer_count, pre_event_count); - - // Seek forward the next filled slot in to the buffer (oldest data) - // from the current position - while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec ) { - pre_index = (pre_index + 1)%image_buffer_count; - // Slot is empty, removing image from counter - pre_event_images--; - } - - event = new Event(this, *(image_buffer[pre_index].timestamp), cause, noteSetMap); - } // end if analysis_fps && pre_event_count - - shared_data->last_event = event->Id(); - //set up video store data - 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, image_count, event->Id()); - - if ( pre_event_images ) { - if ( analysis_fps ) { - for ( int i = 0; i < pre_event_images; i++ ) { - timestamps[i] = pre_event_buffer[pre_index].timestamp; - images[i] = pre_event_buffer[pre_index].image; - pre_index = (pre_index + 1)%pre_event_buffer_count; - } - } else { - for ( int i = 0; i < pre_event_images; i++ ) { - timestamps[i] = image_buffer[pre_index].timestamp; - images[i] = image_buffer[pre_index].image; - pre_index = (pre_index + 1)%image_buffer_count; - } - } - event->AddFrames(pre_event_images, images, timestamps); + Debug(1, "assigning refimage from snap->image"); + ref_image.Assign(*(snap->image)); } - if ( alarm_frame_count ) { - event->SavePreAlarmFrames(); + alarm_image.Assign(*(snap->image)); + } else if (!(analysis_image_count % (motion_frame_skip+1))) { + Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); + // Get new score. + if (0 and snap->in_frame && ( + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) + || + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) + ) ) { + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[0], 0); + snap->score = DetectMotion(v_image, zoneSet); + } else { + snap->score = DetectMotion(*(snap->image), zoneSet); + } + + if (!snap->analysis_image) + snap->analysis_image = new Image(*(snap->image)); + // lets construct alarm cause. It will contain cause + names of zones alarmed + 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); + if (zone.Alarmed()) { + if (!snap->alarm_cause.empty()) snap->alarm_cause += ","; + snap->alarm_cause += std::string(zone.Label()); + if (zone.AlarmImage()) + snap->analysis_image->Overlay(*(zone.AlarmImage())); + } + } + alarm_image.Assign(*(snap->analysis_image)); + Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", + score, last_motion_score, snap->score); + motion_frame_count += 1; + last_motion_score = snap->score; + + if (snap->score) { + if (cause.length()) cause += ", "; + cause += MOTION_CAUSE + std::string(":") + snap->alarm_cause; + noteSetMap[MOTION_CAUSE] = zoneSet; + score += snap->score; + } // end if motion_score + } else { + Debug(1, "Skipped motion detection last motion score was %d", last_motion_score); + alarm_image.Assign(*(snap->image)); + } + } else { + Debug(1, "no image so skipping motion detection"); + } // end if has image + //score += last_motion_score; + } else { + Debug(1, "Not Active(%d) enabled %d shared->active %d doing motion detection: %d", + Active(), enabled, shared_data->active, + (function == MODECT or function == MOCORD) + ); + } // end if active and doing motion detection + + + // If motion detecting, score will be > 0 on motion, but if skipping frames, might not be. So also test snap->score + if ((score > 0) or ((snap->score > 0) 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() + && (event_close_mode == CLOSE_ALARM) + // FIXME since we won't be including this snap in the event if we close it, we should be looking at event->duration() instead + && (event->Duration() >= min_section_length) + && ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count - 1))) { + Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins", + name.c_str(), snap->image_index, event->Id()); + closeEvent(); + } else if (event) { + // This is so if we need more than 1 alarm frame before going into alarm, so it is basically if we have enough alarm frames + Debug(3, + "pre_alarm_count in event %d 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(event->Duration()).count()), + static_cast(Seconds(min_section_length).count()), + (event_close_mode == CLOSE_ALARM)); + } + if ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1)) { + Info("%s: %03d - Gone into alarm state PreAlarmCount: %u > AlarmFrameCount:%u Cause:%s", + name.c_str(), snap->image_index, Event::PreAlarmCount(), alarm_frame_count, cause.c_str()); + shared_data->state = state = ALARM; + + } 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) { + alert_to_alarm_frame_count--; + Info("%s: %03d - Alarmed frame while in alert state. Consecutive alarmed frames left to return to alarm state: %03d", + name.c_str(), analysis_image_count, alert_to_alarm_frame_count); + if (alert_to_alarm_frame_count == 0) { + Info("%s: %03d - Gone back into alarm state", name.c_str(), analysis_image_count); + shared_data->state = state = ALARM; + } + } else if (state == TAPE) { + // Already recording, but IDLE so switch to ALARM + shared_data->state = state = ALARM; + Debug(1, "Was in TAPE, going into ALARM"); + } else { + Debug(1, "Staying in %s", State_Strings[state].c_str()); + } + 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 + + // snap->score -1 means didn't do motion detection so don't do state transition + // In Nodect, we may still have a triggered event, so need this code to run to end the event. + } else if (!score and ((snap->score == 0) or (function == NODECT || function == RECORD))) { + Debug(1, "!score %s", State_Strings[state].c_str()); + alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count + + if (state == ALARM) { + Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count); + shared_data->state = state = ALERT; + } else if (state == ALERT) { + if ( + ((analysis_image_count - last_alarm_count) > post_event_count) + && + (event->Duration() >= min_section_length)) { + Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images", + name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames()); + if ( + (function != RECORD && function != MOCORD) + || + (event_close_mode == CLOSE_ALARM || event_close_mode==CLOSE_IDLE) + ) { + shared_data->state = state = IDLE; + Info("%s: %03d - Closing event %" PRIu64 ", alarm end%s", + name.c_str(), analysis_image_count, event->Id(), (function==MOCORD)?", section truncated":"" ); + closeEvent(); + } else { + shared_data->state = state = TAPE; } } - } else if ( state != PREALARM ) { - Info("%s: %03d - Gone into prealarm state", name, image_count); - shared_data->state = state = PREALARM; + } else if (state == PREALARM) { + // Back to IDLE + shared_data->state = state = ((function != MOCORD) ? IDLE : TAPE); + } else { + 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(snap->timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), + static_cast(Seconds(min_section_length).count())); } - } else if ( state == ALERT ) { - Info("%s: %03d - Gone back into alarm state", name, image_count); - shared_data->state = state = ALARM; - } - last_alarm_count = image_count; - } else { // not score - if ( state == ALARM ) { - Info("%s: %03d - Gone into alert state", name, image_count); - shared_data->state = state = ALERT; - } else if ( state == ALERT ) { - if ( - ( image_count-last_alarm_count > post_event_count ) - && ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= min_section_length ) - ) { - Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images", - name, image_count, event->Id(), event->Frames(), event->AlarmFrames()); - //if ( function != MOCORD || event_close_mode == CLOSE_ALARM || event->Cause() == SIGNAL_CAUSE ) - if ( function != MOCORD || event_close_mode == CLOSE_ALARM ) { - shared_data->state = state = IDLE; - Info("%s: %03d - Closing event %" PRIu64 ", alarm end%s", - name, image_count, event->Id(), (function==MOCORD)?", section truncated":""); + if (Event::PreAlarmCount()) + Event::EmptyPreAlarmFrames(); + } // end if score or not + + if (score > snap->score) + snap->score = score; + + if (state == PREALARM) { + // incremement pre alarm image count + Event::AddPreAlarmFrame(snap->image, snap->timestamp, score, nullptr); + } else if (state == ALARM) { + if (event) { + if (noteSetMap.size() > 0) + event->updateNotes(noteSetMap); + if (section_length >= Seconds(min_section_length) && (event->Duration() >= 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(snap->timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(event->Duration()).count()), + static_cast(Seconds(section_length).count())); closeEvent(); - } else { + event = openEvent(snap, cause, noteSetMap); + } + } else { + if (!event) { + event = openEvent(snap, cause, noteSetMap); + Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_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 == ALERT) { + // Alert means this frame has no motion, but we were alarmed and are still recording. + 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 == 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 (section_length >= Seconds(min_section_length) && (event->Duration() >= section_length) + && ((function == MOCORD && event_close_mode != CLOSE_TIME) + || (function == RECORD && event_close_mode == CLOSE_TIME) + || std::chrono::duration_cast(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) { + Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 , + name.c_str(), + snap->image_index, + event->Id(), + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), + static_cast(Seconds(section_length).count())); + closeEvent(); + } // end if section_length + } // end if event + + if (!event) { + event = openEvent(snap, cause.empty() ? "Continuous" : cause, noteSetMap); + + Info("%s: %03d - Opened new event %" PRIu64 ", continuous section start", + name.c_str(), analysis_image_count, event->Id()); + /* To prevent cancelling out an existing alert\prealarm\alarm state */ + // This ignores current score status. This should all come after the state machine calculations + if (state == IDLE) { shared_data->state = state = TAPE; } - } - } // end if ALARM or ALERT + } // end if ! event + } // end if RECORDING - if ( state == PREALARM ) { - if ( function != MOCORD ) { - shared_data->state = state = IDLE; - } else { - shared_data->state = state = TAPE; - } - } - if ( Event::PreAlarmCount() ) - Event::EmptyPreAlarmFrames(); - } // end if score or not - - if ( state != IDLE ) { - if ( state == PREALARM || state == ALARM ) { - if ( config.create_analysis_images ) { - bool got_anal_image = false; - alarm_image.Assign(*snap_image); - for ( int i = 0; i < n_zones; i++ ) { - if ( zones[i]->Alarmed() ) { - if ( zones[i]->AlarmImage() ) { - alarm_image.Overlay(*(zones[i]->AlarmImage())); - got_anal_image = true; - } - if ( config.record_event_stats && (state == ALARM) ) - zones[i]->RecordStats(event); - } // end if zone is alarmed - } // end foreach zone - - if ( got_anal_image ) { - if ( state == PREALARM ) - Event::AddPreAlarmFrame(snap_image, *timestamp, score, &alarm_image); - else - event->AddFrame(snap_image, *timestamp, score, &alarm_image); - } else { - if ( state == PREALARM ) - Event::AddPreAlarmFrame(snap_image, *timestamp, score); - else - event->AddFrame(snap_image, *timestamp, score); + if (function == MODECT or function == MOCORD) { + if (!ref_image.Buffer()) { + if (0 and snap->in_frame && ( + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) + || + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) + ) ) { + Debug(1, "Assigning from vchannel"); + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[0], 0); + ref_image.Assign(v_image); + } else if (snap->image) { + Debug(1, "Assigning"); + ref_image.Assign(*(snap->image)); } } else { - // Not doing alarm frame storage - if ( state == PREALARM ) { - Event::AddPreAlarmFrame(snap_image, *timestamp, score); - } else { - event->AddFrame(snap_image, *timestamp, score); - if ( config.record_event_stats ) { - for ( int i = 0; i < n_zones; i++ ) { - if ( zones[i]->Alarmed() ) - zones[i]->RecordStats(event); - } - } // end if config.record_event_stats - } - } // end if config.create_analysis_images - - if ( event ) { - if ( noteSetMap.size() > 0 ) - event->updateNotes(noteSetMap); - - if ( section_length - && ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= section_length ) - && ! (image_count % fps_report_interval) + if (0 and snap->in_frame && + ( + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) + || + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) + ) ) { - 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 + Debug(1, "Blending from vchannel"); + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[0], 0); + ref_image.Blend(v_image, ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc )); + } else if (snap->image) { + Debug(1, "Blending because %p and format %d != %d, %d", snap->in_frame, + snap->in_frame->format, + AV_PIX_FMT_YUV420P, + AV_PIX_FMT_YUVJ420P ); - closeEvent(); - event = new Event(this, *timestamp, cause, noteSetMap); - shared_data->last_event = event->Id(); - //set up video store data - snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); - video_store_data->recording = event->StartTime(); - + ref_image.Blend(*(snap->image), ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc )); + Debug(1, "Done Blending"); } - } // end if event - - } else if ( state == ALERT ) { - event->AddFrame(snap_image, *timestamp); - if ( noteSetMap.size() > 0 ) - event->updateNotes(noteSetMap); - } else if ( state == TAPE ) { - //Video Storage: activate only for supported cameras. Event::AddFrame knows whether or not we are recording video and saves frames accordingly - //if((GetOptVideoWriter() == 2) && camera->SupportsNativeVideo()) { - // I don't think this is required, and causes problems, as the event file hasn't been setup yet. - //Warning("In state TAPE, - //video_store_data->recording = event->StartTime(); - //} - if ( (!frame_skip) || !(image_count%(frame_skip+1)) ) { - if ( config.bulk_frame_interval > 1 ) { - event->AddFrame(snap_image, *timestamp, (event->Frames()AddFrame(snap_image, *timestamp); - } - } - } - } // end if ! IDLE - } + } // end if have image + } // end if detecting + last_signal = signal; + } // end if videostream + } // end if signal + shared_data->last_frame_score = score; } else { - if ( event ) { - Info("%s: %03d - Closing event %" PRIu64 ", trigger off", name, image_count, event->Id()); + Debug(3, "trigger == off"); + 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 = TRIGGER_CANCEL; } // end if ( trigger_data->trigger_state != TRIGGER_OFF ) - if ( (!signal_change && signal) && (function == MODECT || function == MOCORD) ) { - if ( state == ALARM ) { - ref_image.Blend( *snap_image, alarm_ref_blend_perc ); - } else { - ref_image.Blend( *snap_image, ref_blend_perc ); - } + 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++; } - last_signal = signal; - } // end if Enabled() - shared_data->last_read_index = index % image_buffer_count; - //shared_data->last_read_time = image_buffer[index].timestamp->tv_sec; - shared_data->last_read_time = now.tv_sec; + if (event) { + event->AddPacket(packet_lock); + } else { + // 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)) { + if (!savejpegs) { + Debug(1, "Deleting image data for %d", snap->image_index); + // Don't need raw images anymore + delete snap->image; + snap->image = nullptr; + } + if (snap->analysis_image and !(savejpegs & 2)) { + Debug(1, "Deleting analysis image data for %d", snap->image_index); + delete snap->analysis_image; + snap->analysis_image = nullptr; + } + } + delete packet_lock; + } + } // end scope for event_lock - if ( analysis_fps && pre_event_buffer_count ) { - // If analysis fps is set, add analysed image to dedicated pre event buffer - int pre_index = image_count%pre_event_buffer_count; - pre_event_buffer[pre_index].image->Assign(*snap->image); - memcpy(pre_event_buffer[pre_index].timestamp, snap->timestamp, sizeof(struct timeval)); - } - - 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::Analyze +} // end Monitor::Analyse void Monitor::Reload() { - Debug(1, "Reloading monitor %s", name); + Debug(1, "Reloading monitor %s", name.c_str()); - if ( event ) { - Info("%s: %03d - Closing event %" PRIu64 ", reloading", name, image_count, event->Id()); + // Access to the event needs to be protected. Either thread could call Reload. Either thread could close the event. + // Need a mutex on it I guess. FIXME + // Need to guard around event creation/deletion This will prevent event creation until new settings are loaded + std::lock_guard lck(event_mutex); + if (event) { + Info("%s: %03d - Closing event %" PRIu64 ", reloading", name.c_str(), image_count, event->Id()); closeEvent(); } - static char sql[ZM_SQL_MED_BUFSIZ]; - // This seems to have fallen out of date. - snprintf(sql, sizeof(sql), - "SELECT `Function`+0, `Enabled`, `LinkedMonitors`, `EventPrefix`, `LabelFormat`, " - "`LabelX`, `LabelY`, `LabelSize`, `WarmupCount`, `PreEventCount`, `PostEventCount`, " - "`AlarmFrameCount`, `SectionLength`, `MinSectionLength`, `FrameSkip`, " - "`MotionFrameSkip`, `AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`, " - "`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, " - "`SignalCheckColour` FROM `Monitors` WHERE `Id` = '%d'", id); - + std::string sql = load_monitor_sql + stringtf(" WHERE Id=%d", id); zmDbRow *row = zmDbFetchOne(sql); - if ( !row ) { + if (!row) { Error("Can't run query: %s", mysql_error(&dbconn)); - } else if ( MYSQL_ROW dbrow = row->mysql_row() ) { - int index = 0; - function = (Function)atoi(dbrow[index++]); - enabled = atoi(dbrow[index++]); - const char *p_linked_monitors = dbrow[index++]; + } else if (MYSQL_ROW dbrow = row->mysql_row()) { + Load(dbrow, true /*load zones */, purpose); - if ( dbrow[index] ) { - strncpy(event_prefix, dbrow[index++], sizeof(event_prefix)-1); - } else { - event_prefix[0] = 0; - index++; - } - if ( dbrow[index] ) { - strncpy(label_format, dbrow[index++], sizeof(label_format)-1); - } else { - label_format[0] = 0; - index++; - } - - label_coord = Coord( atoi(dbrow[index]), atoi(dbrow[index+1]) ); index += 2; - label_size = atoi(dbrow[index++]); - warmup_count = atoi(dbrow[index++]); - pre_event_count = atoi(dbrow[index++]); - post_event_count = atoi(dbrow[index++]); - alarm_frame_count = atoi(dbrow[index++]); - section_length = atoi(dbrow[index++]); - min_section_length = atoi(dbrow[index++]); - frame_skip = atoi(dbrow[index++]); - motion_frame_skip = atoi(dbrow[index++]); - analysis_fps = dbrow[index] ? strtod(dbrow[index], NULL) : 0; index++; - analysis_update_delay = strtoul(dbrow[index++], NULL, 0); - - capture_max_fps = dbrow[index] ? atof(dbrow[index]) : 0.0; index++; - capture_delay = ( capture_max_fps > 0.0 ) ? int(DT_PREC_3/capture_max_fps) : 0; - - alarm_capture_delay = (dbrow[index]&&atof(dbrow[index])>0.0)?int(DT_PREC_3/atof(dbrow[index])):0; index++; - fps_report_interval = atoi(dbrow[index++]); - ref_blend_perc = atoi(dbrow[index++]); - alarm_ref_blend_perc = atoi(dbrow[index++]); - track_motion = atoi(dbrow[index++]); - - signal_check_points = dbrow[index]?atoi(dbrow[index]):0;index++; - - if ( dbrow[index][0] == '#' ) - signal_check_colour = strtol(dbrow[index]+1,0,16); - else - signal_check_colour = strtol(dbrow[index],0,16); - index++; - - shared_data->state = state = IDLE; - shared_data->alarm_x = shared_data->alarm_y = -1; - if ( enabled ) - shared_data->active = true; - ready_count = image_count+warmup_count; - - ReloadLinkedMonitors(p_linked_monitors); delete row; - } // end if row + } // end if row - ReloadZones(); -} // end void Monitor::Reload() +} // 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 = 0; - n_zones = Zone::Load(this, zones); + Debug(3, "Reloading zones for monitor %s have %zu", name.c_str(), zones.size()); + zones = Zone::Load(this); + Debug(1, "Reloading zones for monitor %s have %zu", name.c_str(), zones.size()); + this->AddPrivacyBitmask(); //DumpZoneImage(); } // end void Monitor::ReloadZones() void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) { - Debug(1, "Reloading linked monitors for monitor %s, '%s'", name, p_linked_monitors); + Debug(1, "Reloading linked monitors for monitor %s, '%s'", name.c_str(), p_linked_monitors); if ( n_linked_monitors ) { - for ( int i = 0; i < n_linked_monitors; i++ ) { + for ( int i=0; i < n_linked_monitors; i++ ) { delete linked_monitors[i]; } delete[] linked_monitors; - linked_monitors = 0; + linked_monitors = nullptr; } n_linked_monitors = 0; @@ -1908,13 +2256,14 @@ void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) { int n_link_ids = 0; unsigned int link_ids[256]; + // This nasty code picks out strings of digits from p_linked_monitors and tries to load them. char link_id_str[8]; char *dest_ptr = link_id_str; const char *src_ptr = p_linked_monitors; 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; @@ -1951,619 +2300,193 @@ void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) { for ( int i = 0; i < n_link_ids; i++ ) { Debug(1, "Checking linked monitor %d", link_ids[i]); - db_mutex.lock(); - static char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), + std::string sql = stringtf( "SELECT `Id`, `Name` FROM `Monitors`" " WHERE `Id` = %d" " AND `Function` != 'None'" " AND `Function` != 'Monitor'" " AND `Enabled`=1", - link_ids[i] ); - if ( mysql_query(&dbconn, sql) ) { - db_mutex.unlock(); - Error("Can't run query: %s", mysql_error(&dbconn)); + link_ids[i]); + + MYSQL_RES *result = zmDbFetch(sql); + if (!result) { continue; } - MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - db_mutex.unlock(); - Error("Can't use query result: %s", mysql_error(&dbconn)); - continue; - } - db_mutex.unlock(); int n_monitors = mysql_num_rows(result); if ( n_monitors == 1 ) { MYSQL_ROW dbrow = mysql_fetch_row(result); - Debug(1, "Linking to monitor %d", link_ids[i]); + Debug(1, "Linking to monitor %d %s", atoi(dbrow[0]), dbrow[1]); linked_monitors[count++] = new MonitorLink(link_ids[i], dbrow[1]); } else { Warning("Can't link to monitor %d, invalid id, function or not enabled", link_ids[i]); } mysql_free_result(result); - } // end foreach link_id + } // end foreach link_id n_linked_monitors = count; - } // end if has link_ids - } // end if p_linked_monitors -} // end void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) - -int Monitor::LoadMonitors(std::string sql, Monitor **&monitors, Purpose purpose) { + } // end if has link_ids + } // end if p_linked_monitors +} // end void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) +std::vector> Monitor::LoadMonitors(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()); - if ( !result ) { + MYSQL_RES *result = zmDbFetch(sql); + if (!result) { Error("Can't load local monitors: %s", mysql_error(&dbconn)); - return 0; + return {}; } int n_monitors = mysql_num_rows(result); Debug(1, "Got %d monitors", n_monitors); - delete[] monitors; - monitors = new Monitor *[n_monitors]; - for ( int i=0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { - monitors[i] = Monitor::Load(dbrow, 1, purpose); + + std::vector> monitors; + monitors.reserve(n_monitors); + + for (int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++) { + monitors.emplace_back(std::make_shared()); + monitors.back()->Load(dbrow, true, purpose); } - if ( mysql_errno(&dbconn) ) { + + if (mysql_errno(&dbconn)) { Error("Can't fetch row: %s", mysql_error(&dbconn)); - return 0; + mysql_free_result(result); + return {}; } mysql_free_result(result); - return n_monitors; -} // end int Monitor::LoadMonitors(std::string sql, Monitor **&monitors, Purpose purpose) + return monitors; +} -#if ZM_HAS_V4L -int Monitor::LoadLocalMonitors(const char *device, Monitor **&monitors, Purpose purpose) { +#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, monitors, purpose); -} // end int Monitor::LoadLocalMonitors(const char *device, Monitor **&monitors, Purpose purpose) -#endif // ZM_HAS_V4L + 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_V4L2 -int Monitor::LoadRemoteMonitors(const char *protocol, const char *host, const char *port, const char *path, Monitor **&monitors, Purpose purpose) { - std::string sql = load_monitor_sql + " WHERE `Function` != 'None' AND `Type` = 'Remote'"; - if ( staticConfig.SERVER_ID ) - sql += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID); +std::vector> Monitor::LoadRemoteMonitors +(const char *protocol, const char *host, const char *port, const char *path, Purpose purpose) { + std::string where = "`Function` != 'None' AND `Type` = 'Remote'"; + if (staticConfig.SERVER_ID) + where += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID); + if (protocol) + where += stringtf(" AND `Protocol` = '%s' AND `Host` = '%s' AND `Port` = '%s' AND `Path` = '%s'", protocol, host, port, path); + return LoadMonitors(where, purpose); +} - if ( protocol ) - sql += stringtf(" AND `Protocol` = '%s' AND `Host` = '%s' AND `Port` = '%s' AND `Path` = '%s'", protocol, host, port, path); - return LoadMonitors(sql, monitors, purpose); -} // end int Monitor::LoadRemoteMonitors +std::vector> Monitor::LoadFileMonitors(const char *file, Purpose purpose) { + std::string where = "`Function` != 'None' AND `Type` = 'File'"; + if (file[0]) + where += " AND `Path`='" + std::string(file) + "'"; + if (staticConfig.SERVER_ID) + where += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID); + return LoadMonitors(where, purpose); +} -int Monitor::LoadFileMonitors(const char *file, Monitor **&monitors, Purpose purpose) { - std::string sql = load_monitor_sql + " WHERE `Function` != 'None' AND `Type` = 'File'"; - if ( file[0] ) - sql += " AND `Path`='" + std::string(file) + "'"; - if ( staticConfig.SERVER_ID ) { - sql += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID); - } - return LoadMonitors(sql, monitors, purpose); -} // end int Monitor::LoadFileMonitors - -#if HAVE_LIBAVFORMAT -int Monitor::LoadFfmpegMonitors(const char *file, Monitor **&monitors, Purpose purpose) { - std::string sql = load_monitor_sql + " WHERE `Function` != 'None' AND `Type` = 'Ffmpeg'"; - if ( file[0] ) - sql += " AND `Path` = '" + std::string(file) + "'"; - - if ( staticConfig.SERVER_ID ) { - sql += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID); - } - return LoadMonitors(sql, monitors, purpose); -} // end int Monitor::LoadFfmpegMonitors -#endif // HAVE_LIBAVFORMAT - -/* - std::string load_monitor_sql = - "SELECT Id, Name, ServerId, StorageId, Type, Function+0, Enabled, LinkedMonitors, " - "AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS," - "Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, " // V4L Settings - "Protocol, Method, Options, User, Pass, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, " - "SaveJPEGs, VideoWriter, EncoderParameters, -"OutputCodec, Encoder, OutputContainer," -" RecordAudio, " - "Brightness, Contrast, Hue, Colour, " - "EventPrefix, LabelFormat, LabelX, LabelY, LabelSize," - "ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, " - "SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, " - "FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif, SignalCheckColour FROM Monitors"; -*/ - -Monitor *Monitor::Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose) { - int col = 0; - - int id = atoi(dbrow[col]); col++; - const char *name = dbrow[col]; col++; - int server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++; - int storage_id = atoi(dbrow[col]); col++; - std::string type = dbrow[col] ? dbrow[col] : ""; col++; - Function function = (Function)atoi(dbrow[col]); col++; - int enabled = dbrow[col] ? atoi(dbrow[col]) : 0; col++; - const char *linked_monitors = dbrow[col];col++; - - double analysis_fps = dbrow[col] ? strtod(dbrow[col], NULL) : 0; col++; - unsigned int analysis_update_delay = strtoul(dbrow[col++], NULL, 0); - - double capture_max_fps = dbrow[col] ? atof(dbrow[col]) : 0.0; col++; - double capture_delay = ( capture_max_fps > 0.0 ) ? int(DT_PREC_3/capture_max_fps) : 0; - - Debug(1,"Capture Delay!? %.3f", capture_delay); - unsigned int alarm_capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++; - - const char *device = dbrow[col]; col++; - int channel = atoi(dbrow[col]); col++; - int format = atoi(dbrow[col]); col++; - bool v4l_multi_buffer = config.v4l_multi_buffer; - if ( dbrow[col] ) { - if (*dbrow[col] == '0' ) { - v4l_multi_buffer = false; - } else if ( *dbrow[col] == '1' ) { - v4l_multi_buffer = true; - } - } - col++; - - int v4l_captures_per_frame = 0; - if ( dbrow[col] ) { - v4l_captures_per_frame = atoi(dbrow[col]); - } else { - v4l_captures_per_frame = config.captures_per_frame; - } - Debug(1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame); - col++; - - std::string protocol = dbrow[col] ? dbrow[col] : ""; col++; - std::string method = dbrow[col] ? dbrow[col] : ""; col++; - std::string options = dbrow[col] ? dbrow[col] : ""; col++; - std::string user = dbrow[col] ? dbrow[col] : ""; col++; - std::string pass = dbrow[col] ? dbrow[col] : ""; col++; - std::string host = dbrow[col] ? dbrow[col] : ""; col++; - std::string port = dbrow[col] ? dbrow[col] : ""; col++; - std::string path = dbrow[col] ? dbrow[col] : ""; col++; - int width = atoi(dbrow[col]); col++; - int height = atoi(dbrow[col]); col++; - int colours = atoi(dbrow[col]); col++; - int palette = atoi(dbrow[col]); col++; - Orientation orientation = (Orientation)atoi(dbrow[col]); col++; - int deinterlacing = atoi(dbrow[col]); col++; - std::string decoder_hwaccel_name = dbrow[col] ? dbrow[col] : ""; col++; - std::string decoder_hwaccel_device = dbrow[col] ? dbrow[col] : ""; col++; - - bool rtsp_describe = (dbrow[col] && *dbrow[col] != '0'); col++; - - int savejpegs = atoi(dbrow[col]); col++; - VideoWriter videowriter = (VideoWriter)atoi(dbrow[col]); col++; - const char *encoderparams = dbrow[col] ? dbrow[col] : ""; col++; - bool record_audio = (*dbrow[col] != '0'); col++; - - int brightness = atoi(dbrow[col]); col++; - int contrast = atoi(dbrow[col]); col++; - int hue = atoi(dbrow[col]); col++; - int colour = atoi(dbrow[col]); col++; - - const char *event_prefix = dbrow[col]; col ++; - const char *label_format = dbrow[col] ? dbrow[col] : ""; col ++; - Coord label_coord = Coord( atoi(dbrow[col]), atoi(dbrow[col+1]) ); col += 2; - int label_size = atoi(dbrow[col]); col++; - - int image_buffer_count = atoi(dbrow[col]); col++; - int warmup_count = atoi(dbrow[col]); col++; - int pre_event_count = atoi(dbrow[col]); col++; - int post_event_count = atoi(dbrow[col]); col++; - int stream_replay_buffer = atoi(dbrow[col]); col++; - int alarm_frame_count = atoi(dbrow[col]); col++; - int section_length = atoi(dbrow[col]); col++; - int min_section_length = atoi(dbrow[col]); col++; - int frame_skip = atoi(dbrow[col]); col++; - int motion_frame_skip = atoi(dbrow[col]); col++; - int fps_report_interval = atoi(dbrow[col]); col++; - int ref_blend_perc = atoi(dbrow[col]); col++; - int alarm_ref_blend_perc = atoi(dbrow[col]); col++; - int track_motion = atoi(dbrow[col]); col++; - int signal_check_points = dbrow[col] ? atoi(dbrow[col]) : 0;col++; - int signal_check_color = strtol(dbrow[col][0] == '#' ? dbrow[col]+1 : dbrow[col], 0, 16); col++; - bool embed_exif = (*dbrow[col] != '0'); col++; - - Camera *camera = 0; - if ( type == "Local" ) { - -#if ZM_HAS_V4L - int extras = (deinterlacing>>24)&0xff; - - camera = new LocalCamera( - id, - device, - channel, - format, - v4l_multi_buffer, - v4l_captures_per_frame, - method, - width, - height, - colours, - palette, - brightness, - contrast, - hue, - colour, - purpose==CAPTURE, - record_audio, - extras - ); -#else - Fatal("ZoneMinder not built with Local Camera support"); -#endif - } else if ( type == "Remote" ) { - if ( protocol == "http" ) { - camera = new RemoteCameraHttp( - id, - method, - host, - port, - path, - width, - height, - colours, - brightness, - contrast, - hue, - colour, - purpose==CAPTURE, - record_audio - ); - } -#if HAVE_LIBAVFORMAT - else if ( protocol == "rtsp" ) { - camera = new RemoteCameraRtsp( - id, - method, - host, // Host - port, // Port - path, // Path - width, - height, - rtsp_describe, - colours, - brightness, - contrast, - hue, - colour, - purpose==CAPTURE, - record_audio - ); - } -#endif // HAVE_LIBAVFORMAT - else { - Fatal("Unexpected remote camera protocol '%s'", protocol.c_str()); - } - } else if ( type == "File" ) { - camera = new FileCamera( - id, - path.c_str(), - width, - height, - colours, - brightness, - contrast, - hue, - colour, - purpose==CAPTURE, - record_audio - ); - } else if ( type == "Ffmpeg" ) { -#if HAVE_LIBAVFORMAT - camera = new FfmpegCamera( - id, - path, - method, - options, - width, - height, - colours, - brightness, - contrast, - hue, - colour, - purpose==CAPTURE, - record_audio, - decoder_hwaccel_name, - decoder_hwaccel_device - ); -#endif // HAVE_LIBAVFORMAT - } else if ( type == "NVSocket" ) { - camera = new RemoteCameraNVSocket( - id, - host.c_str(), - port.c_str(), - path.c_str(), - width, - height, - colours, - brightness, - contrast, - hue, - colour, - purpose==CAPTURE, - record_audio - ); - } else if ( type == "Libvlc" ) { -#if HAVE_LIBVLC - camera = new LibvlcCamera( - id, - path.c_str(), - method, - options, - width, - height, - colours, - brightness, - contrast, - hue, - colour, - purpose==CAPTURE, - record_audio - ); -#else // HAVE_LIBVLC - Fatal("You must have vlc libraries installed to use vlc cameras for monitor %d", id); -#endif // HAVE_LIBVLC - } else if ( type == "cURL" ) { -#if HAVE_LIBCURL - camera = new cURLCamera( - id, - path.c_str(), - user.c_str(), - pass.c_str(), - width, - height, - colours, - brightness, - contrast, - hue, - colour, - purpose==CAPTURE, - record_audio - ); -#else // HAVE_LIBCURL - Fatal("You must have libcurl installed to use ffmpeg cameras for monitor %d", id); -#endif // HAVE_LIBCURL - } else { - Fatal("Bogus monitor type '%s' for monitor %d", type.c_str(), id); - } // end if type - - Monitor *monitor = new Monitor( - id, - name, - server_id, - storage_id, - (int)function, - enabled, - linked_monitors, - camera, - orientation, - deinterlacing, - decoder_hwaccel_name, - decoder_hwaccel_device, - savejpegs, - videowriter, - encoderparams, - record_audio, - event_prefix, - label_format, - label_coord, - label_size, - image_buffer_count, - warmup_count, - pre_event_count, - post_event_count, - stream_replay_buffer, - alarm_frame_count, - section_length, - min_section_length, - frame_skip, - motion_frame_skip, - capture_max_fps, - analysis_fps, - analysis_update_delay, - capture_delay, - alarm_capture_delay, - fps_report_interval, - ref_blend_perc, - alarm_ref_blend_perc, - track_motion, - signal_check_points, - signal_check_color, - embed_exif, - purpose, - 0, - 0 - ); - camera->setMonitor(monitor); - Zone **zones = 0; - int n_zones = Zone::Load(monitor, zones); - monitor->AddZones(n_zones, zones); - monitor->AddPrivacyBitmask(zones); - Debug(1, "Loaded monitor %d(%s), %d zones", id, name, n_zones); - return monitor; -} // end Monitor *Monitor::Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose) - -Monitor *Monitor::Load(unsigned int p_id, bool load_zones, Purpose purpose) { - std::string sql = load_monitor_sql + stringtf(" WHERE `Id`=%d", p_id); - - zmDbRow dbrow; - if ( ! dbrow.fetch(sql.c_str()) ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - return NULL; - } - Monitor *monitor = Monitor::Load(dbrow.mysql_row(), load_zones, purpose); - - return monitor; -} // end Monitor *Monitor::Load(unsigned int p_id, bool load_zones, Purpose purpose) +std::vector> Monitor::LoadFfmpegMonitors(const char *file, Purpose purpose) { + std::string where = "`Function` != 'None' AND `Type` = 'Ffmpeg'"; + if (file[0]) + where += " AND `Path` = '" + std::string(file) + "'"; + if (staticConfig.SERVER_ID) + where += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID); + return LoadMonitors(where, purpose); +} /* Returns 0 on success, even if no new images are available (transient error) * Returns -1 on failure. */ int Monitor::Capture() { - static int FirstCapture = 1; // Used in de-interlacing to indicate whether this is the even or odd image - int captureResult; + unsigned int index = image_count % image_buffer_count; + if (image_buffer.empty() or (index >= image_buffer.size())) { + Error("Image Buffer is invalid. Check ImageBufferCount. size is %zu", image_buffer.size()); + return -1; + } - unsigned int index = image_count%image_buffer_count; - Image* capture_image = image_buffer[index].image; + std::shared_ptr packet = std::make_shared(); + packet->image_index = image_count; + 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); - unsigned int deinterlacing_value = deinterlacing & 0xff; - - if ( deinterlacing_value == 4 ) { - if ( FirstCapture != 1 ) { - /* Copy the next image into the shared memory */ - capture_image->CopyBuffer(*(next_buffer.image)); - } - - /* Capture a new next image */ - - //Check if FFMPEG camera - // Icon: I don't think we can support de-interlacing on ffmpeg input.... most of the time it will be h264 or mpeg4 - if ( ( videowriter == H264PASSTHROUGH ) && camera->SupportsNativeVideo() ) { - captureResult = camera->CaptureAndRecord(*(next_buffer.image), - video_store_data->recording, - video_store_data->event_file ); - } else { - captureResult = camera->Capture(*(next_buffer.image)); - } - - if ( FirstCapture ) { - FirstCapture = 0; - return 0; - } - - } else { - //Check if FFMPEG camera - if ( (videowriter == H264PASSTHROUGH) && camera->SupportsNativeVideo() ) { - //Warning("ZMC: Recording: %d", video_store_data->recording); - // Should return -1 on error, like loss of signal. Should return 0 if ok but no video frame. > 0 for received a frame. - captureResult = camera->CaptureAndRecord( - *capture_image, - video_store_data->recording, - video_store_data->event_file - ); - } else { - /* Capture directly into image buffer, avoiding the need to memcpy() */ - captureResult = camera->Capture(*capture_image); - } - } // end if deinterlacing or not - - if ( captureResult < 0 ) { - Info("Return from Capture (%d), signal loss", captureResult); - // Tell zma to end the event. zma will reset TRIGGER - trigger_data->trigger_state = TRIGGER_OFF; - // Unable to capture image for temporary reason + if (captureResult < 0) { + // Unable to capture image // Fake a signal loss image + // Not sure what to do here. We will close monitor and kill analysis_thread but what about rtsp server? Rgb signalcolor; - signalcolor = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR); /* HTML colour code is actually BGR in memory, we want RGB */ + /* HTML colour code is actually BGR in memory, we want RGB */ + signalcolor = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR); + Image *capture_image = new Image(width, height, camera->Colours(), camera->SubpixelOrder()); capture_image->Fill(signalcolor); - } else if ( captureResult > 0 ) { - Debug(4, "Return from Capture (%d)", captureResult); - - /* Deinterlacing */ - if ( deinterlacing_value == 1 ) { - capture_image->Deinterlace_Discard(); - } else if ( deinterlacing_value == 2 ) { - capture_image->Deinterlace_Linear(); - } else if ( deinterlacing_value == 3 ) { - capture_image->Deinterlace_Blend(); - } else if ( deinterlacing_value == 4 ) { - capture_image->Deinterlace_4Field(next_buffer.image, (deinterlacing>>8)&0xff); - } else if ( deinterlacing_value == 5 ) { - capture_image->Deinterlace_Blend_CustomRatio((deinterlacing>>8)&0xff); - } - - if ( orientation != ROTATE_0 ) { - switch ( orientation ) { - case ROTATE_0 : - // No action required - break; - case ROTATE_90 : - case ROTATE_180 : - case ROTATE_270 : - capture_image->Rotate((orientation-1)*90); - break; - case FLIP_HORI : - case FLIP_VERT : - capture_image->Flip(orientation==FLIP_HORI); - break; - } - } // end if have rotation - - if ( capture_image->Size() > camera->ImageSize() ) { - Error("Captured image %d does not match expected size %d check width, height and colour depth", - capture_image->Size(),camera->ImageSize() ); - return -1; - } - - if ( (index == shared_data->last_read_index) && (function > MONITOR) ) { - Warning("Buffer overrun at index %d, image %d, slow down capture, speed up analysis or increase ring buffer size", - index, image_count ); - time_t now = time(0); - double approxFps = double(image_buffer_count)/double(now-image_buffer[index].timestamp->tv_sec); - time_t last_read_delta = now - shared_data->last_read_time; - if ( last_read_delta > (image_buffer_count/approxFps) ) { - Warning("Last image read from shared memory %ld seconds ago, zma may have gone away", last_read_delta); - shared_data->last_read_index = image_buffer_count; - } - } // end if overrun - - if ( privacy_bitmask ) - capture_image->MaskPrivacy(privacy_bitmask); - - // Might be able to remove this call, when we start passing around ZMPackets, which will already have a timestamp - gettimeofday(image_buffer[index].timestamp, NULL); - if ( config.timestamp_on_capture ) { - TimestampImage(capture_image, image_buffer[index].timestamp); - } - // Maybe we don't need to do this on all camera types - shared_data->signal = signal_check_points ? CheckSignal(capture_image) : true; + shared_data->signal = false; shared_data->last_write_index = index; - shared_data->last_write_time = image_buffer[index].timestamp->tv_sec; + 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 = 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->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); + } + video_fifo->writePacket(*packet); + } + } else if (packet->codec_type == AVMEDIA_TYPE_AUDIO) { + if (audio_fifo) + audio_fifo->writePacket(*packet); + + // Only queue if we have some video packets in there. Should push this logic into packetqueue + if (record_audio and (packetqueue.packet_count(video_stream_id) or event)) { + packet->image_index=-1; + Debug(2, "Queueing audio packet"); + packet->packet.stream_index = audio_stream_id; // Convert to packetQueue's index + packetqueue.queuePacket(packet); + } else { + Debug(4, "Not Queueing audio packet"); + } + return 1; + } else { + Debug(1, "Unknown codec type %d", packet->codec_type); + return 1; + } // end if audio image_count++; - if ( image_count && fps_report_interval && ( (!(image_count%fps_report_interval)) || image_count < 5 ) ) { - time_t now = image_buffer[index].timestamp->tv_sec; - // If we are too fast, we get div by zero. This seems to happen in the case of audio packets. - if ( now != last_fps_time ) { - // # of images per interval / the amount of time it took - double new_fps = double(image_count%fps_report_interval?image_count:fps_report_interval)/(now-last_fps_time); - unsigned int new_camera_bytes = camera->Bytes(); - unsigned int new_capture_bandwidth = (new_camera_bytes - last_camera_bytes)/(now-last_fps_time); - last_camera_bytes = new_camera_bytes; - //Info( "%d -> %d -> %d", fps_report_interval, now, last_fps_time ); - //Info( "%d -> %d -> %lf -> %lf", now-last_fps_time, fps_report_interval/(now-last_fps_time), double(fps_report_interval)/(now-last_fps_time), fps ); - Info("%s: images:%d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec", - name, image_count, new_fps, new_capture_bandwidth); - last_fps_time = now; - fps = new_fps; - db_mutex.lock(); - static char sql[ZM_SQL_SML_BUFSIZ]; - // The reason we update the Status as well is because if mysql restarts, the Monitor_Status table is lost, - // and nothing else will update the status until zmc restarts. Since we are successfully capturing we can - // assume that we are connected - snprintf(sql, sizeof(sql), - "INSERT INTO Monitor_Status (MonitorId,CaptureFPS,CaptureBandwidth,Status) " - "VALUES (%d, %.2lf, %u, 'Connected') ON DUPLICATE KEY UPDATE " - "CaptureFPS = %.2lf, CaptureBandwidth=%u, Status='Connected'", - id, fps, new_capture_bandwidth, fps, new_capture_bandwidth); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - } - db_mutex.unlock(); - Debug(4,sql); - } // end if time has changed since last update - } // end if it might be time to report the fps - } // end if captureResult + // Will only be queued if there are iterators allocated in the queue. + 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 ) { @@ -2581,25 +2504,210 @@ int Monitor::Capture() { shared_data->action &= ~SET_SETTINGS; } return captureResult; -} // end int Monitor::Capture +} // 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); + delete 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 = packet->image_index % 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) { + while (!zm_terminate) { + // ICON FIXME SHould we not clone decoder_it? + ZMLockedPacket *deinterlace_packet_lock = packetqueue.get_packet(decoder_it); + if (!deinterlace_packet_lock) { + delete packet_lock; + //packetqueue.unlock(packet_lock); + return false; + } + if (deinterlace_packet_lock->packet_->codec_type == packet->codec_type) { + if (!deinterlace_packet_lock->packet_->image) { + Error("Can't de-interlace when we have to decode. De-Interlacing should only be useful on old low res local cams"); + } else { + capture_image->Deinterlace_4Field(deinterlace_packet_lock->packet_->image, (deinterlacing>>8)&0xff); + } + delete deinterlace_packet_lock; + //packetqueue.unlock(deinterlace_packet_lock); + break; + } + delete deinterlace_packet_lock; + //packetqueue.unlock(deinterlace_packet_lock); + packetqueue.increment_it(decoder_it); + } + if (zm_terminate) { + delete packet_lock; + return false; + } + } else if (deinterlacing_value == 5) { + capture_image->Deinterlace_Blend_CustomRatio((deinterlacing>>8)&0xff); + } + } // end if deinterlacing_value + + 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' : @@ -2607,7 +2715,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; } @@ -2619,220 +2730,279 @@ void Monitor::TimestampImage(Image *ts_image, const struct timeval *ts_time) con *d_ptr++ = *s_ptr++; } // end while *d_ptr = '\0'; - ts_image->Annotate( label_text, label_coord, label_size ); + Debug(2, "annotating %s", label_text); + ts_image->Annotate(label_text, label_coord, label_size); + Debug(2, "done annotating %s", label_text); } // end void Monitor::TimestampImage -bool Monitor::closeEvent() { - if ( !event ) - return false; +Event * Monitor::openEvent( + const std::shared_ptr &snap, + const std::string &cause, + const Event::StringSetMap ¬eSetMap) { - if ( function == RECORD || function == MOCORD ) { - //FIXME Is this neccessary? ENdTime should be set in the destructor - gettimeofday(&(event->EndTime()), NULL); + // FIXME this iterator is not protected from invalidation + packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it( + *analysis_it, + (cause == "Continuous" ? 0 : (pre_event_count > alarm_frame_count ? pre_event_count : alarm_frame_count)) + ); + + // This gets a lock on the starting packet + + 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"); + return nullptr; + } + starting_packet = starting_packet_lock->packet_; + } else { + starting_packet = snap; } - if ( event_delete_thread ) { - event_delete_thread->join(); - delete event_delete_thread; - event_delete_thread = NULL; + + event = new Event(this, starting_packet->timestamp, cause, noteSetMap); + + shared_data->last_event_id = event->Id(); + strncpy(shared_data->alarm_cause, cause.c_str(), sizeof(shared_data->alarm_cause)-1); + + if (!event_start_command.empty()) { + if (fork() == 0) { + execlp(event_start_command.c_str(), event_start_command.c_str(), std::to_string(event->Id()).c_str(), nullptr); + Error("Error execing %s", event_start_command.c_str()); + } } -#if 0 - event_delete_thread = new std::thread([](Event *event) { - Event * e = event; - event = NULL; - delete e; - e = NULL; - }, event); -#else - delete event; - event = NULL; -#endif - video_store_data->recording = (struct timeval){0}; - return true; + + // Write out starting packets, do not modify packetqueue it will garbage collect itself + while (starting_packet and ((*start_it) != *analysis_it)) { + event->AddPacket(starting_packet_lock); + // Have added the packet, don't want to unlock it until we have locked the next + + packetqueue.increment_it(start_it); + if ((*start_it) == *analysis_it) { + //if (starting_packet_lock) delete starting_packet_lock; + break; + } + ZMLockedPacket *lp = packetqueue.get_packet(start_it); + //delete starting_packet_lock; + if (!lp) return nullptr; // only on terminate FIXME + starting_packet_lock = lp; + starting_packet = lp->packet_; + } + packetqueue.free_it(start_it); + delete start_it; + start_it = nullptr; + + return event; +} + +void Monitor::closeEvent() { + if (!event) return; + + 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, const std::string &command){ + int64_t event_id = e->Id(); + delete e; + + if (!command.empty()) { + if (fork() == 0) { + execlp(command.c_str(), command.c_str(), std::to_string(event_id).c_str(), nullptr); + Error("Error execing %s", command.c_str()); + } + } + + }, event, event_end_command); + Debug(1, "Nulling event"); + event = nullptr; + if (shared_data) video_store_data->recording = {}; } // end bool Monitor::closeEvent() unsigned int Monitor::DetectMotion(const Image &comp_image, Event::StringSet &zoneSet) { bool alarm = false; unsigned int score = 0; - if ( n_zones <= 0 ) return alarm; + if (zones.empty()) { + Warning("No zones to check!"); + return alarm; + } ref_image.Delta(comp_image, &delta_image); - if ( config.record_diag_images ) { - ref_image.WriteJpeg(diag_path_r.c_str(), config.record_diag_images_fifo); - delta_image.WriteJpeg(diag_path_d.c_str(), config.record_diag_images_fifo); + if (config.record_diag_images) { + ref_image.WriteJpeg(diag_path_ref, 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(RGB_BLACK, zone->GetPolygon()); + Debug(3, "Blanking inactive zone %s", zone.Label()); + delta_image.Fill(kRGBBlack, zone.GetPolygon()); } // end foreach zone // Check preclusive zones first - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { - Zone *zone = zones[n_zone]; - if ( !zone->IsPreclusive() ) { + for (Zone &zone : zones) { + if (!zone.IsPreclusive()) continue; - } - int old_zone_score = zone->Score(); - bool old_zone_alarmed = zone->Alarmed(); + int old_zone_score = zone.Score(); + bool old_zone_alarmed = zone.Alarmed(); Debug(3, "Checking preclusive zone %s - old score: %d, state: %s", - zone->Label(),old_zone_score, zone->Alarmed()?"alarmed":"quiet"); - if ( zone->CheckAlarms(&delta_image) ) { + zone.Label(),old_zone_score, zone.Alarmed()?"alarmed":"quiet"); + if (zone.CheckAlarms(&delta_image)) { alarm = true; - score += zone->Score(); - zone->SetAlarm(); - Debug(3, "Zone is alarmed, zone score = %d", zone->Score()); - zoneSet.insert(zone->Label()); - //zone->ResetStats(); + score += zone.Score(); + zone.SetAlarm(); + Debug(3, "Zone is alarmed, zone score = %d", zone.Score()); + zoneSet.insert(zone.Label()); } else { // check if end of alarm - if ( old_zone_alarmed ) { + if (old_zone_alarmed) { Debug(3, "Preclusive Zone %s alarm Ends. Previous score: %d", - zone->Label(), old_zone_score); - if ( old_zone_score > 0 ) { - zone->SetExtendAlarmCount(zone->GetExtendAlarmFrames()); + zone.Label(), old_zone_score); + if (old_zone_score > 0) { + zone.SetExtendAlarmCount(zone.GetExtendAlarmFrames()); } - if ( zone->CheckExtendAlarmCount() ) { + if (zone.CheckExtendAlarmCount()) { alarm = true; - zone->SetAlarm(); + zone.SetAlarm(); } else { - zone->ClearAlarm(); + zone.ClearAlarm(); } - } + } // end if zone WAS alarmed } // end if CheckAlarms } // end foreach zone - Coord alarm_centre; + 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, image_count); + shared_data->alarm_x, shared_data->alarm_y, analysis_image_count); } else { shared_data->alarm_x = shared_data->alarm_y = -1; } // This is a small and innocent hack to prevent scores of 0 being returned in alarm state return score ? score : alarm; -} // end MotionDetect +} // end DetectMotion +// TODO: Move the camera specific things to the camera classes and avoid these casts. bool Monitor::DumpSettings(char *output, bool verbose) { output[0] = 0; - sprintf( output+strlen(output), "Id : %d\n", id ); - sprintf( output+strlen(output), "Name : %s\n", name ); + sprintf( output+strlen(output), "Id : %u\n", id ); + sprintf( output+strlen(output), "Name : %s\n", name.c_str() ); sprintf( output+strlen(output), "Type : %s\n", camera->IsLocal()?"Local":(camera->IsRemote()?"Remote":"File") ); -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 if ( camera->IsLocal() ) { - sprintf( output+strlen(output), "Device : %s\n", ((LocalCamera *)camera)->Device().c_str() ); - sprintf( output+strlen(output), "Channel : %d\n", ((LocalCamera *)camera)->Channel() ); - sprintf( output+strlen(output), "Standard : %d\n", ((LocalCamera *)camera)->Standard() ); + LocalCamera* cam = static_cast(camera.get()); + sprintf( output+strlen(output), "Device : %s\n", cam->Device().c_str() ); + sprintf( output+strlen(output), "Channel : %d\n", cam->Channel() ); + sprintf( output+strlen(output), "Standard : %d\n", cam->Standard() ); } else -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 if ( camera->IsRemote() ) { - sprintf( output+strlen(output), "Protocol : %s\n", ((RemoteCamera *)camera)->Protocol().c_str() ); - sprintf( output+strlen(output), "Host : %s\n", ((RemoteCamera *)camera)->Host().c_str() ); - sprintf( output+strlen(output), "Port : %s\n", ((RemoteCamera *)camera)->Port().c_str() ); - sprintf( output+strlen(output), "Path : %s\n", ((RemoteCamera *)camera)->Path().c_str() ); + RemoteCamera* cam = static_cast(camera.get()); + sprintf( output+strlen(output), "Protocol : %s\n", cam->Protocol().c_str() ); + sprintf( output+strlen(output), "Host : %s\n", cam->Host().c_str() ); + sprintf( output+strlen(output), "Port : %s\n", cam->Port().c_str() ); + sprintf( output+strlen(output), "Path : %s\n", cam->Path().c_str() ); } else if ( camera->IsFile() ) { - sprintf( output+strlen(output), "Path : %s\n", ((FileCamera *)camera)->Path() ); + FileCamera* cam = static_cast(camera.get()); + sprintf( output+strlen(output), "Path : %s\n", cam->Path().c_str() ); } -#if HAVE_LIBAVFORMAT else if ( camera->IsFfmpeg() ) { - sprintf( output+strlen(output), "Path : %s\n", ((FfmpegCamera *)camera)->Path().c_str() ); + FfmpegCamera* cam = static_cast(camera.get()); + sprintf( output+strlen(output), "Path : %s\n", cam->Path().c_str() ); } -#endif // HAVE_LIBAVFORMAT - sprintf( output+strlen(output), "Width : %d\n", camera->Width() ); - sprintf( output+strlen(output), "Height : %d\n", camera->Height() ); -#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() ) { - sprintf( output+strlen(output), "Palette : %d\n", ((LocalCamera *)camera)->Palette() ); + LocalCamera* cam = static_cast(camera.get()); + sprintf( output+strlen(output), "Palette : %d\n", cam->Palette() ); } -#endif // ZM_HAS_V4L - sprintf(output+strlen(output), "Colours : %d\n", camera->Colours() ); - sprintf(output+strlen(output), "Subpixel Order : %d\n", camera->SubpixelOrder() ); - sprintf(output+strlen(output), "Event Prefix : %s\n", event_prefix ); - sprintf(output+strlen(output), "Label Format : %s\n", label_format ); - sprintf(output+strlen(output), "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 ); @@ -2840,10 +3010,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); @@ -2855,42 +3025,217 @@ 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(); } -int Monitor::PrimeCapture() const { return camera->PrimeCapture(); } +unsigned int Monitor::Colours() const { return camera ? camera->Colours() : colours; } +unsigned int Monitor::SubpixelOrder() const { return camera ? camera->SubpixelOrder() : 0; } + +int Monitor::PrimeCapture() { + int ret = camera->PrimeCapture(); + if (ret <= 0) return ret; + + if ( -1 != camera->getVideoStreamId() ) { + video_stream_id = packetqueue.addStream(); + } + + if ( -1 != camera->getAudioStreamId() ) { + audio_stream_id = packetqueue.addStream(); + packetqueue.addStream(); + shared_data->audio_frequency = camera->getFrequency(); + shared_data->audio_channels = camera->getChannels(); + } + + Debug(2, "Video stream id is %d, audio is %d, minimum_packets to keep in buffer %d", + video_stream_id, audio_stream_id, pre_event_count); + + if (rtsp_server) { + if (video_stream_id >= 0) { + AVStream *videoStream = camera->getVideoStream(); + snprintf(shared_data->video_fifo_path, sizeof(shared_data->video_fifo_path) - 1, "%s/video_fifo_%u.%s", + staticConfig.PATH_SOCKS.c_str(), + id, + 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 + + //Poller Thread + if (onvif_event_listener || janus_enabled || use_Amcrest_API) { + if (!Poller) { + Poller = zm::make_unique(this); + } else { + Poller->Start(); + } + } + + 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(1, "haveing analysis_it"); + } + if (!analysis_thread) { + Debug(1, "Starting an analysis thread for monitor (%d)", id); + analysis_thread = zm::make_unique(this); + } else { + Debug(1, "Restarting analysis thread for monitor (%d)", id); + analysis_thread->Start(); + } + return ret; +} // end int Monitor::PrimeCapture() + int Monitor::PreCapture() const { return camera->PreCapture(); } -int Monitor::PostCapture() const { return camera->PostCapture() ; } -int Monitor::Close() { return camera->Close(); }; +int Monitor::PostCapture() const { return camera->PostCapture(); } +int Monitor::Close() { + // Because the stream indexes may change we have to clear out the packetqueue + if (decoder) { + decoder->Stop(); + } + if (analysis_thread) { + analysis_thread->Stop(); + } + + //ONVIF Teardown + if (Poller) { + Poller->Stop(); + } +#ifdef WITH_GSOAP + if (onvif_event_listener && (soap != nullptr)) { + Debug(1, "Tearing Down Onvif"); + _wsnt__Unsubscribe wsnt__Unsubscribe; + _wsnt__UnsubscribeResponse wsnt__UnsubscribeResponse; + proxyEvent.Unsubscribe(response.SubscriptionReference.Address, NULL, &wsnt__Unsubscribe, wsnt__UnsubscribeResponse); + soap_destroy(soap); + soap_end(soap); + soap_free(soap); + soap = nullptr; + } //End ONVIF +#endif + //Janus Teardown + if (janus_enabled && (purpose == CAPTURE)) { + delete Janus_Manager; + } + + + packetqueue.clear(); + if (audio_fifo) { + delete audio_fifo; + audio_fifo = nullptr; + } + if (video_fifo) { + delete video_fifo; + video_fifo = nullptr; + } + + if (close_event_thread.joinable()) { + close_event_thread.join(); + } + std::lock_guard lck(event_mutex); + if (event) { + Info("%s: image_count:%d - Closing event %" PRIu64 ", shutting down", name.c_str(), image_count, event->Id()); + closeEvent(); + close_event_thread.join(); + } + if (camera) camera->Close(); + return 1; +} + Monitor::Orientation Monitor::getOrientation() const { return orientation; } -Monitor::Snapshot *Monitor::getSnapshot() const { - return &image_buffer[ shared_data->last_write_index%image_buffer_count ]; -} +// Wait for camera to get an image, and then assign it as the base reference image. +// So this should be done as the first task in the analysis thread startup. +// This function is deprecated. +void Monitor::get_ref_image() { + ZMLockedPacket *snap_lock = nullptr; + + if ( !analysis_it ) + analysis_it = packetqueue.get_video_it(true); + + while ( + ( + !( snap_lock = packetqueue.get_packet(analysis_it)) + or + ( snap_lock->packet_->codec_type != AVMEDIA_TYPE_VIDEO ) + or + ! snap_lock->packet_->image + ) + and !zm_terminate) { + + Debug(1, "Waiting for capture daemon lastwriteindex(%d) lastwritetime(%" PRIi64 ")", + shared_data->last_write_index, static_cast(shared_data->last_write_time)); + if (snap_lock and ! snap_lock->packet_->image) { + delete snap_lock; + // can't analyse it anyways, incremement + packetqueue.increment_it(analysis_it); + } + } + 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) { + ref_image.Assign(width, height, camera->Colours(), + camera->SubpixelOrder(), snap->image->Buffer(), camera->ImageSize()); + Debug(2, "Have ref image about to unlock"); + } else { + Debug(2, "Have no ref image about to unlock"); + } + delete snap_lock; +} // 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 ) { + "(SELECT `GroupId` FROM `Groups_Monitors` WHERE `MonitorId`=%d)", id); + 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); @@ -2902,7 +3247,40 @@ 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() + +#ifdef WITH_GSOAP +//ONVIF Set Credentials +void Monitor::set_credentials(struct soap *soap) +{ + soap_wsse_delete_Security(soap); + soap_wsse_add_Timestamp(soap, NULL, 10); + soap_wsse_add_UsernameTokenDigest(soap, "Auth", onvif_username.c_str(), onvif_password.c_str()); +} + +//GSOAP boilerplate +int SOAP_ENV__Fault(struct soap *soap, char *faultcode, char *faultstring, char *faultactor, struct SOAP_ENV__Detail *detail, struct SOAP_ENV__Code *SOAP_ENV__Code, struct SOAP_ENV__Reason *SOAP_ENV__Reason, char *SOAP_ENV__Node, char *SOAP_ENV__Role, struct SOAP_ENV__Detail *SOAP_ENV__Detail) +{ + // populate the fault struct from the operation arguments to print it + soap_fault(soap); + // SOAP 1.1 + soap->fault->faultcode = faultcode; + soap->fault->faultstring = faultstring; + soap->fault->faultactor = faultactor; + soap->fault->detail = detail; + // SOAP 1.2 + soap->fault->SOAP_ENV__Code = SOAP_ENV__Code; + soap->fault->SOAP_ENV__Reason = SOAP_ENV__Reason; + soap->fault->SOAP_ENV__Node = SOAP_ENV__Node; + soap->fault->SOAP_ENV__Role = SOAP_ENV__Role; + soap->fault->SOAP_ENV__Detail = SOAP_ENV__Detail; + // set error + soap->error = SOAP_FAULT; + // handle or display the fault here with soap_stream_fault(soap, std::cerr); + // return HTTP 202 Accepted + return soap_send_empty_response(soap, SOAP_OK); +} +#endif diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 9109c9c72..30bc57d87 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -1,50 +1,54 @@ // // ZoneMinder Monitor Class Interfaces, $Date$, $Revision$ // Copyright (C) 2001-2008 Philip Coombes -// +// // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// +// #ifndef ZM_MONITOR_H #define ZM_MONITOR_H -#include -#include -#include - -#include "zm.h" -#include "zm_coord.h" -#include "zm_image.h" -#include "zm_rgb.h" -#include "zm_zone.h" -#include "zm_event.h" -class Monitor; -#include "zm_group.h" +#include "zm_define.h" #include "zm_camera.h" -#include "zm_storage.h" +#include "zm_analysis_thread.h" +#include "zm_poll_thread.h" +#include "zm_decoder_thread.h" +#include "zm_event.h" +#include "zm_fifo.h" +#include "zm_image.h" +#include "zm_packet.h" +#include "zm_packetqueue.h" #include "zm_utils.h" - -#include "zm_image_analyser.h" - +#include #include -#include +#include +#include + +#ifdef WITH_GSOAP +#include "soapPullPointSubscriptionBindingProxy.h" +#include "plugin/wsseapi.h" +#include +#endif + +class Group; #define SIGNAL_CAUSE "Signal" #define MOTION_CAUSE "Motion" #define LINKED_CAUSE "Linked" + // // This is the main class for monitors. Each monitor is associated // with a camera and is effectively a collector for events. @@ -69,15 +73,17 @@ public: } Function; typedef enum { - LOCAL, + LOCAL=1, REMOTE, FILE, FFMPEG, LIBVLC, - CURL, + LIBCURL, + NVSOCKET, + VNC, } CameraType; - typedef enum { + typedef enum { ROTATE_0=1, ROTATE_90, ROTATE_180, @@ -87,6 +93,23 @@ public: } Orientation; typedef enum { + 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, @@ -96,8 +119,8 @@ public: typedef enum { DISABLED, - X264ENCODE, - H264PASSTHROUGH, + ENCODE, + PASSTHROUGH, } VideoWriter; protected: @@ -107,13 +130,15 @@ protected: typedef enum { CLOSE_TIME, CLOSE_IDLE, CLOSE_ALARM } EventCloseMode; - /* sizeof(SharedData) expected to be 340 bytes on 32bit and 64bit */ + /* sizeof(SharedData) expected to be 472 bytes on 32bit and 64bit */ typedef struct { uint32_t size; /* +0 */ - uint32_t last_write_index; /* +4 */ - uint32_t last_read_index; /* +8 */ + int32_t last_write_index; /* +4 */ + int32_t last_read_index; /* +8 */ uint32_t state; /* +12 */ - uint64_t last_event; /* +16 */ + double capture_fps; // Current capturing fps + double analysis_fps; // Current analysis fps + uint64_t last_event_id; /* +16 */ uint32_t action; /* +24 */ int32_t brightness; /* +28 */ int32_t hue; /* +32 */ @@ -126,37 +151,49 @@ protected: uint8_t signal; /* +54 */ uint8_t format; /* +55 */ uint32_t imagesize; /* +56 */ - uint32_t epadding1; /* +60 */ - /* + uint32_t last_frame_score; /* +60 */ + uint32_t audio_frequency; /* +64 */ + uint32_t audio_channels; /* +68 */ + /* ** This keeps 32bit time_t and 64bit time_t identical and compatible as long as time is before 2038. ** Shared memory layout should be identical for both 32bit and 64bit and is multiples of 16. - ** Because startup_time is 64bit it may be aligned to a 64bit boundary. So it's offset SHOULD be a multiple + ** 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 */ - time_t last_write_time; + 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 last_read_time; + union { /* +88 */ + time_t last_write_time; uint64_t extrapad3; }; - uint8_t control_state[256]; /* +88 */ + union { /* +96 */ + time_t last_read_time; + uint64_t extrapad4; + }; + uint8_t control_state[256]; /* +104 */ char alarm_cause[256]; - + char video_fifo_path[64]; + char audio_fifo_path[64]; + } SharedData; - typedef enum { TRIGGER_CANCEL, TRIGGER_ON, TRIGGER_OFF } TriggerState; + enum TriggerState : uint32 { + TRIGGER_CANCEL, + TRIGGER_ON, + TRIGGER_OFF + }; /* sizeof(TriggerData) expected to be 560 on 32bit & and 64bit */ typedef struct { uint32_t size; - uint32_t trigger_state; + TriggerState trigger_state; uint32_t trigger_score; uint32_t padding; char trigger_cause[32]; @@ -164,26 +201,15 @@ protected: char trigger_showtext[256]; } TriggerData; - /* sizeof(Snapshot) expected to be 16 bytes on 32bit and 32 bytes on 64bit */ - struct Snapshot { - struct timeval *timestamp; - Image *image; - void* padding; - }; - //TODO: Technically we can't exclude this struct when people don't have avformat as the Memory.pm module doesn't know about avformat -#if 1 //sizeOf(VideoStoreData) expected to be 4104 bytes on 32bit and 64bit typedef struct { uint32_t size; uint64_t current_event; char event_file[4096]; timeval recording; // used as both bool and a pointer to the timestamp when recording should begin - //uint32_t frameNumber; } VideoStoreData; -#endif // HAVE_LIBAVFORMAT - class MonitorLink { protected: unsigned int id; @@ -194,7 +220,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 @@ -206,24 +232,20 @@ protected: volatile VideoStoreData *video_store_data; int last_state; - uint64_t last_event; + uint64_t last_event_id; public: - MonitorLink( int p_id, const char *p_name ); + MonitorLink(unsigned int p_id, const char *p_name); ~MonitorLink(); - inline int Id() const { - return id; - } - inline const char *Name() const { - return( name ); - } + inline unsigned int Id() const { return id; } + inline const char *Name() const { return name; } - inline bool isConnected() const { - return( connected ); - } - inline time_t getLastConnectTime() const { - return( last_connect_time ); + inline bool isConnected() const { return connected && shared_data->valid; } + inline time_t getLastConnectTime() const { return last_connect_time; } + + inline uint32_t lastFrameScore() { + return shared_data->last_frame_score; } bool connect(); @@ -234,97 +256,176 @@ protected: bool hasAlarmed(); }; + class AmcrestAPI { protected: + Monitor *parent; + std::string amcrest_response; + CURLM *curl_multi = nullptr; + CURL *Amcrest_handle = nullptr; + static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp); + + public: + AmcrestAPI( Monitor *parent_); + ~AmcrestAPI(); + int API_Connect(); + void WaitForMessage(); + bool Amcrest_Alarmed; + int start_Amcrest(); + }; + + class JanusManager { + protected: + Monitor *parent; + CURL *curl = nullptr; + //helper class for CURL + static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp); + bool Janus_Healthy; + std::string janus_session; + std::string janus_handle; + std::string janus_endpoint; + std::string stream_key; + std::string rtsp_username; + std::string rtsp_password; + std::string rtsp_path; + + public: + JanusManager(Monitor *parent_); + ~JanusManager(); + int add_to_janus(); + int check_janus(); + int remove_from_janus(); + int get_janus_session(); + int get_janus_handle(); + int get_janus_plugin(); + std::string get_stream_key(); + }; + + // These are read from the DB and thereafter remain unchanged unsigned int id; - char name[64]; + std::string name; unsigned int server_id; // Id of the Server object unsigned int storage_id; // Id of the Storage Object, which currently will just provide a path, but in future may do more. CameraType type; Function function; // What the monitor is doing bool enabled; // Whether the monitor is enabled or asleep + bool decoding_enabled; // Whether the monitor will decode h264/h265 packets + bool janus_enabled; // Whether we set the h264/h265 stream up on janus + bool janus_audio_enabled; // Whether we tell Janus to try to include audio. + + std::string protocol; + std::string method; + std::string options; + std::string host; + std::string port; + std::string user; + std::string pass; + std::string path; + std::string second_path; + + std::string onvif_url; + std::string onvif_username; + std::string onvif_password; + std::string onvif_options; + bool onvif_event_listener; + bool use_Amcrest_API; + + std::string device; + int palette; + int channel; + int format; + + unsigned int camera_width; + unsigned int camera_height; unsigned int width; // Normally the same as the camera, but not if partly rotated unsigned int height; // Normally the same as the camera, but not if partly rotated bool v4l_multi_buffer; unsigned int v4l_captures_per_frame; Orientation orientation; // Whether the image has to be rotated at all unsigned int deinterlacing; - bool videoRecording; + unsigned int deinterlacing_value; std::string decoder_hwaccel_name; std::string decoder_hwaccel_device; + bool videoRecording; + bool rtsp_describe; - int savejpegs; - VideoWriter videowriter; - std::string encoderparams; - std::vector encoderparamsvec; - bool record_audio; // Whether to store the audio that we receive + int savejpegs; + int colours; + VideoWriter videowriter; + std::string encoderparams; + int output_codec; + std::string encoder; + std::string output_container; + _AVPIXELFORMAT imagePixFormat; + bool record_audio; // Whether to store the audio that we receive - int brightness; // The statically saved brightness of the camera - int contrast; // The statically saved contrast of the camera - int hue; // The statically saved hue of the camera - int colour; // The statically saved colour of the camera - char event_prefix[64]; // The prefix applied to event names as they are created - char label_format[64]; // The format of the timestamp on the images - Coord label_coord; // The coordinates of the timestamp on the images - int label_size; // Size of the timestamp on the images - int image_buffer_count; // Size of circular image buffer, at least twice the size of the pre_event_count - int pre_event_buffer_count; // Size of dedicated circular pre event buffer used when analysis is not performed at capturing framerate, - // value is pre_event_count + alarm_frame_count - 1 - int warmup_count; // How many images to process before looking for events - int pre_event_count; // How many images to hold and prepend to an alarm event - int post_event_count; // How many unalarmed images must occur before the alarm state is reset - struct timeval video_buffer_duration; // How long a video segment to keep in buffer (set only if analysis fps != 0 ) - int stream_replay_buffer; // How many frames to store to support DVR functions, IGNORED from this object, passed directly into zms now - int section_length; // How long events should last in continuous modes - int min_section_length; // Minimum event length when using event_close_mode == ALARM - bool adaptive_skip; // Whether to use the newer adaptive algorithm for this monitor - int frame_skip; // How many frames to skip in continuous modes - int motion_frame_skip; // How many frames to skip in motion detection - double capture_max_fps; // Target Capture FPS - double analysis_fps; // Target framerate for video analysis - unsigned int analysis_update_delay; // How long we wait before updating analysis parameters - int capture_delay; // How long we wait between capture frames - int alarm_capture_delay; // How long we wait between capture frames when in alarm state - int alarm_frame_count; // How many alarm frames are required before an event is triggered - int fps_report_interval; // How many images should be captured/processed between reporting the current FPS - int ref_blend_perc; // Percentage of new image going into reference image. - int alarm_ref_blend_perc; // Percentage of new image going into reference image during alarm. - bool track_motion; // Whether this monitor tries to track detected motion - int signal_check_points; // Number of points in the image to check for signal - Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected - bool embed_exif; // Whether to embed Exif data into each image frame or not - bool last_signal; + int brightness; // The statically saved brightness of the camera + int contrast; // The statically saved contrast of the camera + int hue; // The statically saved hue of the camera + int colour; // The statically saved colour of the camera - double fps; - unsigned int last_camera_bytes; - - Image delta_image; - Image ref_image; - Image alarm_image; // Used in creating analysis images, will be initialized in Analysis - Image write_image; // Used when creating snapshot images - std::string diag_path_r; - std::string diag_path_d; + 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, 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 + 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 + 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 + int fps_report_interval; // How many images should be captured/processed between reporting the current FPS + int ref_blend_perc; // Percentage of new image going into reference image. + int alarm_ref_blend_perc; // Percentage of new image going into reference image during alarm. + bool track_motion; // Whether this monitor tries to track detected motion + int signal_check_points; // Number of points in the image to check for signal + Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected + bool embed_exif; // Whether to embed Exif data into each image frame or not + bool rtsp_server; // Whether to include this monitor as an rtsp server stream + std::string rtsp_streamname; // path in the rtsp url for this monitor + int importance; // Importance of this monitor, affects Connection logging errors. + + int capture_max_fps; Purpose purpose; // What this monitor has been created to do - int event_count; - int image_count; - int ready_count; - int first_alarm_count; - int last_alarm_count; - int buffer_count; - int prealarm_count; - State state; - time_t start_time; - time_t last_fps_time; - time_t auto_resume_time; + unsigned int last_camera_bytes; + + int event_count; + int image_count; + int last_capture_image_count; // last value of image_count when calculating capture fps + int analysis_image_count; // How many frames have been processed by analysis thread. + int motion_frame_count; // How many frames have had motion detection performed on them. + int last_motion_frame_count; // last value of motion_frame_count when calculating fps + int ready_count; + int first_alarm_count; + int last_alarm_count; + bool last_signal; + int buffer_count; + State state; + 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 @@ -334,164 +435,232 @@ protected: TriggerData *trigger_data; VideoStoreData *video_store_data; - Snapshot *image_buffer; - Snapshot next_buffer; /* Used by four field deinterlacing */ - Snapshot *pre_event_buffer; + struct timeval *shared_timestamps; + unsigned char *shared_images; + std::vector image_buffer; - Camera *camera; + int video_stream_id; // will be filled in PrimeCapture + int audio_stream_id; // will be filled in PrimeCapture + Fifo *video_fifo; + Fifo *audio_fifo; + + std::unique_ptr camera; Event *event; + std::mutex event_mutex; Storage *storage; - int n_zones; - Zone **zones; + VideoStore *videoStore; + PacketQueue packetqueue; + std::unique_ptr Poller; + packetqueue_iterator *analysis_it; + std::unique_ptr analysis_thread; + packetqueue_iterator *decoder_it; + std::unique_ptr decoder; + AVFrame *dest_frame; // Used by decoding thread doing colorspace conversions + SwsContext *convert_context; + std::thread close_event_thread; - struct timeval **timestamps; - Image **images; + std::vector zones; const unsigned char *privacy_bitmask; - std::thread *event_delete_thread; // Used to close events, but continue processing. int n_linked_monitors; MonitorLink **linked_monitors; + std::string event_start_command; + std::string event_end_command; std::vector groups; -public: - explicit Monitor( int p_id ); + Image delta_image; + Image ref_image; + Image alarm_image; // Used in creating analysis images, will be initialized in Analysis + Image write_image; // Used when creating snapshot images + std::string diag_path_ref; + std::string diag_path_delta; + + //ONVIF + bool Poll_Trigger_State; + bool Event_Poller_Healthy; + bool Event_Poller_Closes_Event; + + JanusManager *Janus_Manager; + AmcrestAPI *Amcrest_Manager; + +#ifdef WITH_GSOAP + struct soap *soap = nullptr; + _tev__CreatePullPointSubscription request; + _tev__CreatePullPointSubscriptionResponse response; + _tev__PullMessages tev__PullMessages; + _tev__PullMessagesResponse tev__PullMessagesResponse; + PullPointSubscriptionBindingProxy proxyEvent; + void set_credentials(struct soap *soap); +#endif + + + // 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(); -// OurCheckAlarms seems to be unused. Check it on zm_monitor.cpp for more info. -//bool OurCheckAlarms( Zone *zone, const Image *pImage ); - Monitor( - int p_id, - const char *p_name, - unsigned int p_server_id, - unsigned int p_storage_id, - int p_function, - bool p_enabled, - const char *p_linked_monitors, - Camera *p_camera, - int p_orientation, - unsigned int p_deinterlacing, - const std::string &p_decoder_hwaccel_name, - const std::string &p_decoder_hwaccel_device, - int p_savejpegs, - VideoWriter p_videowriter, - std::string p_encoderparams, - bool p_record_audio, - const char *p_event_prefix, - const char *p_label_format, - const Coord &p_label_coord, - int label_size, - int p_image_buffer_count, - int p_warmup_count, - int p_pre_event_count, - int p_post_event_count, - int p_stream_replay_buffer, - int p_alarm_frame_count, - int p_section_length, - int p_min_section_length, - int p_frame_skip, - int p_motion_frame_skip, - double p_capture_max_fps, - double p_analysis_fps, - unsigned int p_analysis_update_delay, - int p_capture_delay, - int p_alarm_capture_delay, - int p_fps_report_interval, - int p_ref_blend_perc, - int p_alarm_ref_blend_perc, - bool p_track_motion, - int p_signal_check_points, - Rgb p_signal_check_colour, - bool p_embed_exif, - Purpose p_purpose, - int p_n_zones=0, - Zone *p_zones[]=0 - ); ~Monitor(); - void AddZones( int p_n_zones, Zone *p_zones[] ); - void AddPrivacyBitmask( Zone *p_zones[] ); + void AddPrivacyBitmask(); + void LoadCamera(); bool connect(); + bool disconnect(); + inline bool isConnected() const { return mem_ptr != nullptr; } + inline int ShmValid() const { - return( shared_data->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 int Id() const { - return id; - } - inline const char *Name() const { - return name; - } + inline unsigned int Id() const { return id; } + inline const char *Name() const { return name.c_str(); } + inline unsigned int ServerId() const { return server_id; } inline Storage *getStorage() { if ( ! storage ) { - storage = new Storage( storage_id ); + storage = new Storage(storage_id); } return storage; } - inline Function GetFunction() const { - return( function ); - } - inline bool Enabled() { + inline CameraType GetType() const { return type; } + inline Function GetFunction() const { return function; } + inline PacketQueue * GetPacketQueue() { return &packetqueue; } + inline bool Enabled() const { if ( function <= MONITOR ) return false; return enabled; } - inline const char *EventPrefix() const { - return event_prefix; + inline bool DecodingEnabled() const { + return decoding_enabled; } - inline bool Ready() { - if ( function <= MONITOR ) - return false; - return( image_count > ready_count ); + bool JanusEnabled() { + return janus_enabled; } - inline bool Active() { + bool JanusAudioEnabled() { + return janus_audio_enabled; + } + bool OnvifEnabled() { + return onvif_event_listener; + } + int check_janus(); //returns 1 for healthy, 0 for success but missing stream, negative for error. + bool EventPollerHealthy() { + return Event_Poller_Healthy; + } + inline const char *EventPrefix() const { return event_prefix.c_str(); } + inline bool Ready() const { + if ( image_count >= ready_count ) { + return true; + } + Debug(2, "Not ready because image_count(%d) <= ready_count(%d)", image_count, ready_count); + return false; + } + inline bool Active() const { if ( function <= MONITOR ) return false; return( enabled && shared_data->active ); } - inline bool Exif() { - return embed_exif; - } + inline bool Exif() const { return embed_exif; } + inline bool RTSPServer() const { return rtsp_server; } + inline bool RecordAudio() const { return record_audio; } + + /* + inline Purpose Purpose() { return purpose }; + inline Purpose Purpose( Purpose p ) { purpose = p; }; + */ + Orientation getOrientation() const; unsigned int Width() const { return width; } unsigned int Height() const { return height; } unsigned int Colours() const; unsigned int SubpixelOrder() const; - + + int GetAudioFrequency() const { return shared_data ? shared_data->audio_frequency : -1; } + int GetAudioChannels() const { return shared_data ? shared_data->audio_channels : -1; } + int GetOptSaveJPEGs() const { return savejpegs; } VideoWriter GetOptVideoWriter() const { return videowriter; } - const std::vector* GetOptEncoderParams() const { return &encoderparamsvec; } + const std::string &GetEncoderOptions() const { return encoderparams; } + int OutputCodec() const { return output_codec; } + const std::string &Encoder() const { return encoder; } + const std::string &OutputContainer() const { return output_container; } + uint64_t GetVideoWriterEventId() const { return video_store_data->current_event; } - void SetVideoWriterEventId( unsigned long long p_event_id ) { video_store_data->current_event = p_event_id; } - struct timeval GetVideoWriterStartTime() const { return video_store_data->recording; } - void SetVideoWriterStartTime(struct timeval &t) { video_store_data->recording = t; } - + void SetVideoWriterEventId( uint64_t p_event_id ) { video_store_data->current_event = p_event_id; } + + 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; }; - struct timeval GetVideoBufferDuration() const { return video_buffer_duration; }; - int GetImageBufferCount() const { return image_buffer_count; }; - State GetState() const; - int GetImage( int index=-1, int scale=100 ); - Snapshot *getSnapshot() const; - struct timeval GetTimestamp( int index=-1 ) const; + int32_t GetImageBufferCount() const { return image_buffer_count; }; + State GetState() const { return (State)shared_data->state; } + + AVStream *GetAudioStream() const { return camera ? camera->getAudioStream() : nullptr; }; + AVCodecContext *GetAudioCodecContext() const { return camera ? camera->getAudioCodecContext() : nullptr; }; + AVStream *GetVideoStream() const { return camera ? camera->getVideoStream() : nullptr; }; + AVCodecContext *GetVideoCodecContext() const { return camera ? camera->getVideoCodecContext() : nullptr; }; + + 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; }; + + const std::string &getONVIF_URL() const { return onvif_url; }; + const std::string &getONVIF_Username() const { return onvif_username; }; + const std::string &getONVIF_Password() const { return onvif_password; }; + const std::string &getONVIF_Options() const { return onvif_options; }; + + Image *GetAlarmImage(); + int GetImage(int32_t index=-1, int scale=100); + ZMPacket *getSnapshot( 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 UpdateFPS(); void ForceAlarmOn( int force_score, const char *force_case, const char *force_text="" ); void ForceAlarmOff(); void CancelForced(); - TriggerState GetTriggerState() const { return (TriggerState)(trigger_data?trigger_data->trigger_state:TRIGGER_CANCEL); } - inline time_t getStartupTime() const { return shared_data->startup_time; } - inline void setStartupTime( time_t p_time ) { shared_data->startup_time = p_time; } + TriggerState GetTriggerState() const { return trigger_data ? trigger_data->trigger_state : TRIGGER_CANCEL; } + 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; } void actionReload(); void actionEnable(); @@ -499,25 +668,37 @@ 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() const; + int PrimeCapture(); int PreCapture() const; int Capture(); int PostCapture() const; int Close(); + void CheckAction(); + unsigned int DetectMotion( const Image &comp_image, Event::StringSet &zoneSet ); // DetectBlack seems to be unused. Check it on zm_monitor.cpp for more info. //unsigned int DetectBlack( const Image &comp_image, Event::StringSet &zoneSet ); bool CheckSignal( const Image *image ); bool Analyse(); + bool Decode(); + bool Poll(); 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; + Event *openEvent( + const std::shared_ptr &snap, + const std::string &cause, + const Event::StringSetMap ¬eSetMap); + void closeEvent(); void Reload(); void ReloadZones(); @@ -528,24 +709,27 @@ public: std::vector Groups(); StringVector GroupNames(); - static int LoadMonitors(std::string sql, Monitor **&monitors, Purpose purpose); // Returns # of Monitors loaded, 0 on failure. -#if ZM_HAS_V4L - static int LoadLocalMonitors(const char *device, Monitor **&monitors, Purpose purpose); -#endif // ZM_HAS_V4L - static int LoadRemoteMonitors(const char *protocol, const char *host, const char*port, const char*path, Monitor **&monitors, Purpose purpose); - static int LoadFileMonitors(const char *file, Monitor **&monitors, Purpose purpose); -#if HAVE_LIBAVFORMAT - static int LoadFfmpegMonitors(const char *file, Monitor **&monitors, Purpose purpose); -#endif // HAVE_LIBAVFORMAT - static Monitor *Load(unsigned int id, bool load_zones, Purpose purpose); - static Monitor *Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose); + static std::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_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); + static std::vector> LoadFfmpegMonitors(const char *file, Purpose purpose); + static std::shared_ptr Load(unsigned int id, bool load_zones, Purpose purpose); + void Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose); //void writeStreamImage( Image *image, struct timeval *timestamp, int scale, int mag, int x, int y ); //void StreamImages( int scale=100, int maxfps=10, time_t ttl=0, int msq_id=0 ); //void StreamImagesRaw( int scale=100, int maxfps=10, time_t ttl=0 ); //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_monitor_amcrest.cpp b/src/zm_monitor_amcrest.cpp new file mode 100644 index 000000000..04af8734c --- /dev/null +++ b/src/zm_monitor_amcrest.cpp @@ -0,0 +1,126 @@ +// +// ZoneMinder Monitor::AmcrestAPI Class Implementation, $Date$, $Revision$ +// Copyright (C) 2022 Jonathan Bennett +// +// 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_monitor.h" + + +Monitor::AmcrestAPI::AmcrestAPI(Monitor *parent_) { + parent = parent_; + curl_multi = curl_multi_init(); + start_Amcrest(); +} + +Monitor::AmcrestAPI::~AmcrestAPI() { + if (Amcrest_handle != nullptr) { //potentially clean up the old handle + curl_multi_remove_handle(curl_multi, Amcrest_handle); + curl_easy_cleanup(Amcrest_handle); + } + if (curl_multi != nullptr) curl_multi_cleanup(curl_multi); +} + +int Monitor::AmcrestAPI::start_Amcrest() { + //init the transfer and start it in multi-handle + int running_handles; + long response_code; + struct CURLMsg *m; + CURLMcode curl_error; + if (Amcrest_handle != nullptr) { //potentially clean up the old handle + curl_multi_remove_handle(curl_multi, Amcrest_handle); + curl_easy_cleanup(Amcrest_handle); + } + + std::string full_url = parent->onvif_url; + if (full_url.back() != '/') full_url += '/'; + full_url += "eventManager.cgi?action=attach&codes=[VideoMotion]"; + Amcrest_handle = curl_easy_init(); + if (!Amcrest_handle){ + Warning("Handle is null!"); + return -1; + } + curl_easy_setopt(Amcrest_handle, CURLOPT_URL, full_url.c_str()); + curl_easy_setopt(Amcrest_handle, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(Amcrest_handle, CURLOPT_WRITEDATA, &amcrest_response); + curl_easy_setopt(Amcrest_handle, CURLOPT_USERNAME, parent->onvif_username.c_str()); + curl_easy_setopt(Amcrest_handle, CURLOPT_PASSWORD, parent->onvif_password.c_str()); + curl_easy_setopt(Amcrest_handle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + curl_error = curl_multi_add_handle(curl_multi, Amcrest_handle); + if (curl_error != CURLM_OK) { + Warning("error of %i", curl_error); + } + curl_error = curl_multi_perform(curl_multi, &running_handles); + if (curl_error == CURLM_OK) { + curl_easy_getinfo(Amcrest_handle, CURLINFO_RESPONSE_CODE, &response_code); + int msgq = 0; + m = curl_multi_info_read(curl_multi, &msgq); + if (m && (m->msg == CURLMSG_DONE)) { + Warning("Libcurl exited Early: %i", m->data.result); + } + + curl_multi_wait(curl_multi, NULL, 0, 300, NULL); + curl_error = curl_multi_perform(curl_multi, &running_handles); + } + + if ((curl_error == CURLM_OK) && (running_handles > 0)) { + parent->Event_Poller_Healthy = TRUE; + } else { + Warning("Response: %s", amcrest_response.c_str()); + Warning("Seeing %i streams, and error of %i, url: %s", running_handles, curl_error, full_url.c_str()); + curl_easy_getinfo(Amcrest_handle, CURLINFO_OS_ERRNO, &response_code); + Warning("Response code: %lu", response_code); + } + +return 0; +} + +void Monitor::AmcrestAPI::WaitForMessage() { + int open_handles; + int transfers; + curl_multi_perform(curl_multi, &open_handles); + if (open_handles == 0) { + start_Amcrest(); //http transfer ended, need to restart. + } else { + curl_multi_wait(curl_multi, NULL, 0, 5000, &transfers); //wait for max 5 seconds for event. + if (transfers > 0) { //have data to deal with + curl_multi_perform(curl_multi, &open_handles); //actually grabs the data, populates amcrest_response + if (amcrest_response.find("action=Start") != std::string::npos) { + //Event Start + Debug(1,"Triggered on ONVIF"); + if (!parent->Poll_Trigger_State) { + Debug(1,"Triggered Event"); + parent->Poll_Trigger_State = TRUE; + } + } else if (amcrest_response.find("action=Stop") != std::string::npos){ + Debug(1, "Triggered off ONVIF"); + parent->Poll_Trigger_State = FALSE; + if (!parent->Event_Poller_Closes_Event) { //If we get a close event, then we know to expect them. + parent->Event_Poller_Closes_Event = TRUE; + Debug(1,"Setting ClosesEvent"); + } + } + amcrest_response.clear(); //We've dealt with the message, need to clear the queue + } + } + return; +} + +size_t Monitor::AmcrestAPI::WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) +{ + ((std::string*)userp)->append((char*)contents, size * nmemb); + return size * nmemb; +} diff --git a/src/zm_monitor_janus.cpp b/src/zm_monitor_janus.cpp new file mode 100644 index 000000000..2fe9b3f26 --- /dev/null +++ b/src/zm_monitor_janus.cpp @@ -0,0 +1,316 @@ +// +// ZoneMinder Monitor::JanusManager Class Implementation, $Date$, $Revision$ +// Copyright (C) 2022 Jonathan Bennett +// +// 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_monitor.h" + + +Monitor::JanusManager::JanusManager(Monitor *parent_) { //constructor takes care of init and calls add_to + std::string response; + std::size_t pos; + parent = parent_; + if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) { + janus_endpoint = config.janus_path; //TODO: strip trailing / + } else { + janus_endpoint = "127.0.0.1:8088/janus"; + } + if (janus_endpoint.back() == '/') janus_endpoint.pop_back(); //remove the trailing slash if present + std::size_t pos2 = parent->path.find("@", pos); + if (pos2 != std::string::npos) { //If we find an @ symbol, we have a username/password. Otherwise, passwordless login. + + std::size_t pos = parent->path.find(":", 7); //Search for the colon, but only after the RTSP:// text. + if (pos == std::string::npos) throw std::runtime_error("Cannot Parse URL for Janus."); //Looks like an invalid url + rtsp_username = parent->path.substr(7, pos-7); + + rtsp_password = parent->path.substr(pos+1, pos2 - pos - 1); + rtsp_path = "RTSP://"; + rtsp_path += parent->path.substr(pos2 + 1); + + } else { + rtsp_username = ""; + rtsp_password = ""; + rtsp_path = parent->path; + } +} + +Monitor::JanusManager::~JanusManager() { + if (janus_session.empty()) get_janus_session(); + if (janus_handle.empty()) get_janus_handle(); + + std::string response; + std::string endpoint; + + std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; + //std::size_t pos; + CURLcode res; + + curl = curl_easy_init(); + if(!curl) return; + + endpoint = janus_endpoint; + endpoint += "/"; + endpoint += janus_session; + endpoint += "/"; + endpoint += janus_handle; + + //Assemble our actual request + postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; + postData += "\"request\" : \"destroy\", \"admin_key\" : \""; + postData += config.janus_secret; + postData += "\", \"id\" : "; + postData += std::to_string(parent->id); + postData += "}}"; + + curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res)); + curl_easy_cleanup(curl); + return; + } + + Debug(1, "Removed stream from Janus: %s", response.c_str()); + curl_easy_cleanup(curl); + return; +} + + + +int Monitor::JanusManager::check_janus() { + if (janus_session.empty()) get_janus_session(); + if (janus_handle.empty()) get_janus_handle(); + + std::string response; + std::string endpoint = janus_endpoint; + std::string postData; + //std::size_t pos; + CURLcode res; + + curl = curl_easy_init(); + if(!curl) return -1; + + endpoint += "/"; + endpoint += janus_session; + endpoint += "/"; + endpoint += janus_handle; + + //Assemble our actual request + postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; + postData += "\"request\" : \"info\", \"id\" : "; + postData += std::to_string(parent->id); + postData += "}}"; + + curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) { //may mean an error code thrown by Janus, because of a bad session + Warning("Attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res)); + curl_easy_cleanup(curl); + janus_session = ""; + janus_handle = ""; + return -1; + } + + curl_easy_cleanup(curl); + Debug(1, "Queried for stream status: %s", response.c_str()); + if (response.find("\"janus\": \"error\"") != std::string::npos) { + if (response.find("No such session") != std::string::npos) { + Warning("Janus Session timed out"); + janus_session = ""; + return -2; + } else if (response.find("No such handle") != std::string::npos) { + Warning("Janus Handle timed out"); + janus_handle = ""; + return -2; + } + } else if (response.find("No such mountpoint") != std::string::npos) { + Warning("Mountpoint Missing"); + return 0; + } + return 1; +} + +int Monitor::JanusManager::add_to_janus() { + if (janus_session.empty()) get_janus_session(); + if (janus_handle.empty()) get_janus_handle(); + + std::string response; + std::string endpoint = janus_endpoint; + + CURLcode res; + + curl = curl_easy_init(); + if (!curl) { + Error("Failed to init curl"); + return -1; + } + + endpoint += "/"; + endpoint += janus_session; + endpoint += "/"; + endpoint += janus_handle; + + //Assemble our actual request + std::string postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; + postData += "\"request\" : \"create\", \"admin_key\" : \""; + postData += config.janus_secret; + postData += "\", \"type\" : \"rtsp\", "; + postData += "\"url\" : \""; + postData += rtsp_path; + if (rtsp_username != "") { + postData += "\", \"rtsp_user\" : \""; + postData += rtsp_username; + postData += "\", \"rtsp_pwd\" : \""; + postData += rtsp_password; + } + postData += "\", \"id\" : "; + postData += std::to_string(parent->id); + if (parent->janus_audio_enabled) postData += ", \"audio\" : true"; + postData += ", \"video\" : true}}"; + Warning("Sending %s to %s", postData.c_str(), endpoint.c_str()); + + curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + Error("Failed to curl_easy_perform adding rtsp stream"); + curl_easy_cleanup(curl); + return -1; + } + if (response.find("\"janus\": \"error\"") != std::string::npos) { + if (response.find("No such session") != std::string::npos) { + Warning("Janus Session timed out"); + janus_session = ""; + return -2; + } else if (response.find("No such handle") != std::string::npos) { + Warning("Janus Handle timed out"); + janus_handle = ""; + return -2; + } + } + //scan for missing session or handle id "No such session" "no such handle" + + Debug(1,"Added stream to Janus: %s", response.c_str()); + curl_easy_cleanup(curl); + return 0; +} + + +size_t Monitor::JanusManager::WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) +{ + ((std::string*)userp)->append((char*)contents, size * nmemb); + return size * nmemb; +} + +/* +void Monitor::JanusManager::generateKey() +{ + const std::string CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + std::random_device random_device; + std::mt19937 generator(random_device()); + std::uniform_int_distribution<> distribution(0, CHARACTERS.size() - 1); + + std::string random_string; + + for (std::size_t i = 0; i < 16; ++i) + { + random_string += CHARACTERS[distribution(generator)]; + } + + stream_key = random_string; +} +*/ + + +int Monitor::JanusManager::get_janus_session() { + janus_session = ""; + std::string endpoint = janus_endpoint; + + std::string response; + + std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; + std::size_t pos; + CURLcode res; + curl = curl_easy_init(); + if(!curl) return -1; + + curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res)); + curl_easy_cleanup(curl); + return -1; + } + + pos = response.find("\"id\": "); + if (pos == std::string::npos) + { + curl_easy_cleanup(curl); + return -1; + } + janus_session = response.substr(pos + 6, 16); + curl_easy_cleanup(curl); + return 1; + +} //get_janus_session + +int Monitor::JanusManager::get_janus_handle() { + std::string response = ""; + std::string endpoint = janus_endpoint; + std::size_t pos; + + CURLcode res; + curl = curl_easy_init(); + if(!curl) return -1; + + endpoint += "/"; + endpoint += janus_session; + std::string postData = "{\"janus\" : \"attach\", \"plugin\" : \"janus.plugin.streaming\", \"transaction\" : \"randomString\"}"; + curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) + { + Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res)); + curl_easy_cleanup(curl); + return -1; + } + + pos = response.find("\"id\": "); + if (pos == std::string::npos) + { + curl_easy_cleanup(curl); + return -1; + } + janus_handle = response.substr(pos + 6, 16); + curl_easy_cleanup(curl); + return 1; +} //get_janus_handle diff --git a/src/zm_monitor_monitorlink.cpp b/src/zm_monitor_monitorlink.cpp new file mode 100644 index 000000000..95432388a --- /dev/null +++ b/src/zm_monitor_monitorlink.cpp @@ -0,0 +1,198 @@ +// +// ZoneMinder Monitor 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_monitor.h" + +#include + +#if ZM_MEM_MAPPED +#include +#include +#else // ZM_MEM_MAPPED +#include +#include +#endif // ZM_MEM_MAPPED + +Monitor::MonitorLink::MonitorLink(unsigned int p_id, const char *p_name) : + id(p_id), + shared_data(nullptr), + trigger_data(nullptr), + video_store_data(nullptr) +{ + strncpy(name, p_name, sizeof(name)-1); + +#if ZM_MEM_MAPPED + map_fd = -1; + mem_file = stringtf("%s/zm.mmap.%u", staticConfig.PATH_MAP.c_str(), id); +#else // ZM_MEM_MAPPED + shm_id = 0; +#endif // ZM_MEM_MAPPED + mem_size = 0; + mem_ptr = nullptr; + + last_event_id = 0; + last_state = IDLE; + + last_connect_time = 0; + connected = false; +} + +Monitor::MonitorLink::~MonitorLink() { + disconnect(); +} + +bool Monitor::MonitorLink::connect() { + 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=%jd", static_cast(mem_size)); +#if ZM_MEM_MAPPED + 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) { + 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); + map_fd = new_map_fd; + } + + struct stat map_stat; + 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.c_str(), strerror(errno)); + disconnect(); + return false; + } 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 (%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) { + Debug(3, "Can't shmget link memory: %s", strerror(errno)); + connected = false; + return false; + } + mem_ptr = (unsigned char *)shmat(shm_id, 0, 0); + if ((int)mem_ptr == -1) { + Debug(3, "Can't shmat link memory: %s", strerror(errno)); + connected = false; + return false; + } +#endif // ZM_MEM_MAPPED + + shared_data = (SharedData *)mem_ptr; + trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData)); + + if (!shared_data->valid) { + Debug(3, "Linked memory not initialised by capture daemon"); + disconnect(); + return false; + } + + last_state = shared_data->state; + last_event_id = shared_data->last_event_id; + connected = true; + + return true; + } + return false; +} // end bool Monitor::MonitorLink::connect() + +bool Monitor::MonitorLink::disconnect() { + if (connected) { + connected = false; + +#if ZM_MEM_MAPPED + if (mem_ptr > (void *)0) { + msync(mem_ptr, mem_size, MS_ASYNC); + munmap(mem_ptr, mem_size); + } + 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; + } + + 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 (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; +} + +bool Monitor::MonitorLink::isAlarmed() { + if (!connected) { + return false; + } + return( shared_data->state == ALARM ); +} + +bool Monitor::MonitorLink::inAlarm() { + if (!connected) { + return false; + } + return( shared_data->state == ALARM || shared_data->state == ALERT ); +} + +bool Monitor::MonitorLink::hasAlarmed() { + if (shared_data->state == ALARM) { + return true; + } + last_event_id = shared_data->last_event_id; + return false; +} diff --git a/src/zm_monitorstream.cpp b/src/zm_monitorstream.cpp index 49d617f7d..a6232a23e 100644 --- a/src/zm_monitorstream.cpp +++ b/src/zm_monitorstream.cpp @@ -17,23 +17,25 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" -#include "zm_db.h" -#include "zm_time.h" -#include "zm_mpeg.h" -#include "zm_signal.h" -#include "zm_monitor.h" #include "zm_monitorstream.h" + +#include "zm_monitor.h" +#include "zm_signal.h" +#include "zm_time.h" #include #include +#include +#include +#include -const int MAX_SLEEP_USEC=1000000; // 1 sec +#ifdef __FreeBSD__ +#include +#endif bool MonitorStream::checkSwapPath(const char *path, bool create_path) { - struct stat stat_buf; if ( stat(path, &stat_buf) < 0 ) { - if ( create_path && errno == ENOENT ) { + if ( create_path and (errno == ENOENT) ) { Debug(3, "Swap path '%s' missing, creating", path); if ( mkdir(path, 0755) ) { Error("Can't mkdir %s: %s", path, strerror(errno)); @@ -73,21 +75,21 @@ bool MonitorStream::checkSwapPath(const char *path, bool create_path) { return false; } return true; -} // end bool MonitorStream::checkSwapPath( const char *path, bool create_path ) +} // end bool MonitorStream::checkSwapPath(const char *path, bool create_path) void MonitorStream::processCommand(const CmdMsg *msg) { - Debug( 2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0] ); + Debug(2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0]); // Check for incoming command - switch( (MsgCommand)msg->msg_data[0] ) { + switch ( (MsgCommand)msg->msg_data[0] ) { case CMD_PAUSE : 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; } @@ -95,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; } @@ -108,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; } @@ -132,28 +134,40 @@ void MonitorStream::processCommand(const CmdMsg *msg) { break; } break; + case CMD_MAXFPS : + { + 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]; + + maxfps = (int_part + dec_part / 1000000.0); + + Debug(1, "Got MAXFPS %f", maxfps); + 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; @@ -175,7 +189,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) { case CMD_ZOOMIN : x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; - Debug( 1, "Got ZOOM IN command, to %d,%d", x, y ); + Debug(1, "Got ZOOM IN command, to %d,%d", x, y); switch ( zoom ) { case 100: zoom = 150; @@ -196,7 +210,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) { } break; case CMD_ZOOMOUT : - Debug( 1, "Got ZOOM OUT command" ); + Debug(1, "Got ZOOM OUT command"); switch ( zoom ) { case 500: zoom = 400; @@ -227,6 +241,15 @@ void MonitorStream::processCommand(const CmdMsg *msg) { break; case CMD_QUIT : Info("User initiated exit - CMD_QUIT"); + zm_terminate = true; + break; + case CMD_ANALYZE_ON : + frame_type = FRAME_ANALYSIS; + Debug(1, "ANALYSIS on"); + break; + case CMD_ANALYZE_OFF : + frame_type = FRAME_NORMAL; + Debug(1, "ANALYSIS off"); break; case CMD_QUERY : Debug(1, "Got QUERY command, sending STATUS"); @@ -240,6 +263,8 @@ void MonitorStream::processCommand(const CmdMsg *msg) { int id; int state; double fps; + double capture_fps; + double analysis_fps; int buffer_level; int rate; double delay; @@ -251,30 +276,53 @@ void MonitorStream::processCommand(const CmdMsg *msg) { } status_data; status_data.id = monitor->Id(); - status_data.fps = monitor->GetFPS(); - status_data.state = monitor->shared_data->state; - if ( playback_buffer > 0 ) - status_data.buffer_level = (MOD_ADD( (temp_write_index-temp_read_index), 0, temp_image_buffer_count )*100)/temp_image_buffer_count; - else + if (!monitor->ShmValid()) { + status_data.fps = 0.0; + status_data.capture_fps = 0.0; + status_data.analysis_fps = 0.0; + status_data.state = Monitor::UNKNOWN; + //status_data.enabled = monitor->shared_data->active; + status_data.enabled = false; + status_data.forced = false; status_data.buffer_level = 0; + } else { + FPSeconds elapsed = now - last_fps_update; + if (elapsed.count()) { + actual_fps = (actual_fps + (frame_count - last_frame_count) / elapsed.count())/2; + 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; + //status_data.enabled = monitor->shared_data->active; + status_data.enabled = monitor->trigger_data->trigger_state != Monitor::TriggerState::TRIGGER_OFF; + status_data.forced = monitor->trigger_data->trigger_state == Monitor::TriggerState::TRIGGER_ON; + if ( playback_buffer > 0 ) + status_data.buffer_level = (MOD_ADD( (temp_write_index-temp_read_index), 0, temp_image_buffer_count )*100)/temp_image_buffer_count; + else + status_data.buffer_level = 0; + } 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_sent).count(); status_data.zoom = zoom; - //status_data.enabled = monitor->shared_data->active; - status_data.enabled = monitor->trigger_data->trigger_state!=Monitor::TRIGGER_OFF; - status_data.forced = monitor->trigger_data->trigger_state==Monitor::TRIGGER_ON; - Debug(2, "Buffer Level:%d, Delayed:%d, Paused:%d, Rate:%d, delay:%.3f, Zoom:%d, Enabled:%d Forced:%d", - status_data.buffer_level, - status_data.delayed, - status_data.paused, - status_data.rate, - status_data.delay, - status_data.zoom, - status_data.enabled, - status_data.forced - ); + Debug(2, "viewing fps: %.2f capture_fps: %.2f analysis_fps: %.2f Buffer Level:%d, Delayed:%d, Paused:%d, Rate:%d, delay:%.3f, Zoom:%d, Enabled:%d Forced:%d", + status_data.fps, + status_data.capture_fps, + status_data.analysis_fps, + status_data.buffer_level, + status_data.delayed, + status_data.paused, + status_data.rate, + status_data.delay, + status_data.zoom, + status_data.enabled, + status_data.forced + ); DataMsg status_msg; status_msg.msg_type = MSG_DATA_WATCH; @@ -283,116 +331,111 @@ void MonitorStream::processCommand(const CmdMsg *msg) { if ( (nbytes = sendto(sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr))) < 0 ) { //if ( errno != EAGAIN ) { - Error( "Can't sendto on sd %d: %s", sd, strerror(errno) ); + Error("Can't sendto on sd %d: %s", sd, strerror(errno)); //exit( -1 ); } } Debug(2, "Number of bytes sent to (%s): (%d)", rem_addr.sun_path, nbytes); +} // end void MonitorStream::processCommand(const CmdMsg *msg) - // quit after sending a status, if this was a quit request - if ( (MsgCommand)msg->msg_data[0]==CMD_QUIT ) { - Debug(2,"Quitting"); - exit(0); - } - - 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 ) - send_raw = false; - if ( !config.timestamp_on_capture && timestamp ) + if ( + ( type != STREAM_JPEG ) + || + (!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 = NULL; - 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, NULL); + TimePoint send_start_time = std::chrono::steady_clock::now(); - fputs("--ZoneMinderFrame\r\nContent-Type: image/jpeg\r\n", stdout); - fprintf(stdout, "Content-Length: %d\r\n\r\n", img_buffer_size); - if ( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 ) { + if ( + (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) + ) { if ( !zm_terminate ) Warning("Unable to send stream frame: %s", strerror(errno)); return false; } - fputs("\r\n\r\n", stdout); + fputs("\r\n", stdout); fflush(stdout); - struct timeval frameEndTime; - gettimeofday(&frameEndTime, NULL); + if (maxfps > 0.0) { + 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 ) { - maxfps /= 2; - Info("Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps); + if (frame_send_time > Milliseconds(lround(Milliseconds::period::den / 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) { + if (!config.timestamp_on_capture) { + monitor->TimestampImage(image, timestamp); + } Image *send_image = prepareImage(image); - if ( !config.timestamp_on_capture && timestamp ) - monitor->TimestampImage(send_image, timestamp); -#if HAVE_LIBAVCODEC + fputs("--" BOUNDARY "\r\n", stdout); + // Calculate how long it takes to actually send the frame + TimePoint send_start_time = std::chrono::steady_clock::now(); + 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, NULL); - fputs("--ZoneMinderFrame\r\n", stdout); - switch( type ) { + switch ( type ) { case STREAM_JPEG : send_image->EncodeJpeg(img_buffer, &img_buffer_size); fputs("Content-Type: image/jpeg\r\n", stdout); 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 : @@ -402,67 +445,94 @@ bool MonitorStream::sendFrame(Image *image, struct timeval *timestamp) { send_image->Zip(img_buffer, &zip_buffer_size); img_buffer_size = zip_buffer_size; #else - Error("zlib is required for zipped images. Falling back to raw image"); - type = STREAM_RAW; + Error("zlib is required for zipped images. Falling back to raw image"); + type = STREAM_RAW; #endif // HAVE_ZLIB_H break; default : Error("Unexpected frame type %d", type); return false; } - fprintf(stdout, "Content-Length: %d\r\n\r\n", img_buffer_size); - if ( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 ) { + if ( + (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) + ) { if ( !zm_terminate ) { // If the pipe was closed, we will get signalled SIGPIPE to exit, which will set zm_terminate Warning("Unable to send stream frame: %s", strerror(errno)); } return false; } - fputs("\r\n\r\n", stdout); + fputs("\r\n", stdout); fflush(stdout); - struct timeval frameEndTime; - gettimeofday(&frameEndTime, NULL); - - int frameSendTime = tvDiffMsec(frameStartTime, frameEndTime); - if ( frameSendTime > 1000/maxfps ) { - maxfps /= 1.5; - Warning("Frame send time %d msec too slow, throttling maxfps to %.2f", - frameSendTime, maxfps); - } } // Not mpeg - last_frame_sent = TV_2_FLOAT(now); + + last_frame_sent = std::chrono::steady_clock::now(); + if (maxfps > 0.0) { + TimePoint::duration frame_send_time = last_frame_sent - send_start_time; + TimePoint::duration maxfps_milliseconds = Milliseconds(lround(Milliseconds::period::den / maxfps)); + + if (frame_send_time > maxfps_milliseconds) { + //maxfps /= 1.5; + Warning("Frame send time %" PRIi64 " msec too slow (> %" PRIi64 ", throttling maxfps to %.3f", + static_cast(std::chrono::duration_cast(frame_send_time).count()), + static_cast(std::chrono::duration_cast(maxfps_milliseconds).count()), + maxfps); + } + } return true; -} // end bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) +} // end bool MonitorStream::sendFrame(Image *image, SystemTimePoint 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 ( !checkInitialised() ) { - Error("Not initialized"); - return; + if (type == STREAM_JPEG) + fputs("Content-Type: multipart/x-mixed-replace; boundary=" BOUNDARY "\r\n\r\n", stdout); + + /* 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; updateFrameRate(monitor->GetFPS()); - if ( type == STREAM_JPEG ) - fputs("Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n", stdout); - // point to end which is theoretically not a valid value because all indexes are % image_buffer_count - unsigned int last_read_index = monitor->image_buffer_count; + int32_t last_read_index = monitor->image_buffer_count; - time_t stream_start_time; - time(&stream_start_time); + TimePoint stream_start_time = std::chrono::steady_clock::now(); + when_to_send_next_frame = stream_start_time; // initialize it to now so that we spit out a frame immediately frame_count = 0; - temp_image_buffer = 0; + temp_image_buffer = nullptr; temp_image_buffer_count = playback_buffer; temp_read_index = temp_image_buffer_count; temp_write_index = temp_image_buffer_count; @@ -471,188 +541,162 @@ void MonitorStream::runStream() { bool buffered_playback = false; // Last image and timestamp when paused, will be resent occasionally to prevent timeout - Image *paused_image = NULL; - struct timeval paused_timestamp; + Image *paused_image = nullptr; + 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; int swap_path_length = staticConfig.PATH_SWAP.length() + 1; // +1 for NULL terminator - int subfolder1_length = snprintf(NULL, 0, "/zmswap-m%d", monitor->Id()) + 1; - int subfolder2_length = snprintf(NULL, 0, "/zmswap-q%06d", connkey) + 1; + int subfolder1_length = snprintf(nullptr, 0, "/zmswap-m%u", monitor->Id()) + 1; + int subfolder2_length = snprintf(nullptr, 0, "/zmswap-q%06d", connkey) + 1; int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length; - 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 - float max_secs_since_last_sent_frame = 10.0; //should be > keep alive amount (5 secs) - // 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; - } - - if ( capture_fps < 1 ) { - max_secs_since_last_sent_frame = 10/capture_fps; - Debug(1, "Adjusting max_secs_since_last_sent_frame to %.2f from current fps %.2f", - max_secs_since_last_sent_frame, monitor->GetFPS()); - } else { - Debug(1, "Not Adjusting max_secs_since_last_sent_frame to %.2f from current fps %.2f", - max_secs_since_last_sent_frame, monitor->GetFPS()); - } - - 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, NULL); + now = std::chrono::steady_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 = NULL; + 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; + TimePoint::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 @@ -661,80 +705,103 @@ 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 ( now >= when_to_send_next_frame ) { + if (!paused && !delayed) { last_read_index = monitor->shared_data->last_write_index; - Debug(2, "index: %d: frame_mod: %d frame count: %d paused(%d) delayed(%d)", + Debug(2, "Sending frame index: %d: frame_mod: %d frame count: %d paused(%d) delayed(%d)", index, frame_mod, frame_count, paused, delayed); // Send the next frame - Monitor::Snapshot *snap = &monitor->image_buffer[index]; + // + // Perhaps we should use NOW instead. + last_frame_timestamp = + SystemTimePoint(zm::chrono::duration_cast(monitor->shared_timestamps[index])); - Debug(2, "sending Frame."); - if ( !sendFrame(snap->image, snap->timestamp) ) { + Image *send_image = nullptr; + if ((frame_type == FRAME_ANALYSIS) && + (monitor->GetFunction() == Monitor::MOCORD || monitor->GetFunction() == Monitor::MODECT)) { + Debug(1, "Sending analysis image"); + send_image = monitor->GetAlarmImage(); + if (!send_image) { + Debug(1, "Falling back"); + send_image = monitor->image_buffer[index]; + } + } else { + Debug(1, "Sending regular image index %d", index); + send_image = monitor->image_buffer[index]; + } + + if (!sendFrame(send_image, last_frame_timestamp)) { Debug(2, "sendFrame failed, quiting."); zm_terminate = true; + break; + } + 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(send_image, last_frame_timestamp)) { + Debug(2, "sendFrame failed, quiting."); + zm_terminate = true; + break; + } } - // Perhaps we should use NOW instead. - memcpy( - &last_frame_timestamp, - snap->timestamp, - sizeof(last_frame_timestamp) - ); - // frame_sent = true; temp_read_index = temp_write_index; } else { - 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 ) { + TimePoint::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 - } // end if should send frame + } // end if actual_delta_time > 5 + } // end if change in zoom + } // end if paused or not + //} else { + //frame_count++; + } // end if should send frame now > when_to_send_next_frame - if ( buffered_playback && !paused ) { - if ( monitor->shared_data->valid ) { - if ( monitor->image_buffer[index].timestamp->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; } - memcpy(&(temp_image_buffer[temp_index].timestamp), monitor->image_buffer[index].timestamp, sizeof(temp_image_buffer[0].timestamp)); - monitor->image_buffer[index].image->WriteJpeg(temp_image_buffer[temp_index].file_name, config.jpeg_file_quality); + + temp_image_buffer[temp_index].timestamp = + 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; @@ -748,72 +815,85 @@ 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); + Debug(3, "Waiting for capture last_write_index=%u == last_read_index=%u", + monitor->shared_data->last_write_index, + last_read_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))); - Debug(3, "Sleeping for (%d)", sleep_time); + FPSeconds sleep_time; + if (now >= when_to_send_next_frame) { + // sent a frame, so update - if ( sleep_time > MAX_SLEEP_USEC ) { - // Shouldn't sleep for long because we need to check command queue, etc. - sleep_time = MAX_SLEEP_USEC; - } - Debug(3, "Sleeping for %dus", sleep_time); - usleep(sleep_time); - if ( ttl ) { - if ( (now.tv_sec - stream_start_time) > ttl ) { - Debug(2, "now(%d) - start(%d) > ttl(%d) break", now.tv_sec, stream_start_time, ttl); - break; + double capture_fps = monitor->GetFPS(); + double fps = ((maxfps > 0.0) && (capture_fps > maxfps)) ? maxfps : capture_fps; + double sleep_time_seconds = (1 / ((fps ? fps : 1))) // 1 second / fps + * (replay_rate ? abs(replay_rate)/ZM_RATE_BASE : 1); // replay_rate is 100 for 1x + Debug(3, "Using %f for maxfps. capture_fps: %f maxfps %f * replay_rate: %d = %f", fps, capture_fps, maxfps, replay_rate, sleep_time_seconds); + + sleep_time = FPSeconds(sleep_time_seconds); + if (when_to_send_next_frame > now) + sleep_time -= when_to_send_next_frame - now; + + when_to_send_next_frame = now + std::chrono::duration_cast(sleep_time); + + if (last_frame_sent > now) { + FPSeconds elapsed = last_frame_sent - now; + if (sleep_time > elapsed) { + sleep_time -= elapsed; + } } + } else { + sleep_time = when_to_send_next_frame - now; } - if ( !last_frame_sent ) { - // 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; - Warning("no last_frame_sent. Shouldn't happen. frame_mod was (%d) frame_count (%d)", - frame_mod, frame_count); - } else if ( - (!paused) - && - ( (TV_2_FLOAT(now) - last_frame_sent) > max_secs_since_last_sent_frame ) - ) { - Error("Terminating, last frame sent time %f secs more than maximum of %f", - TV_2_FLOAT(now) - last_frame_sent, max_secs_since_last_sent_frame); + + if (sleep_time > MonitorStream::MAX_SLEEP) { + Debug(3, "Sleeping for MAX_SLEEP_USEC instead of %" PRIi64 " us", + static_cast(std::chrono::duration_cast(sleep_time).count())); + // Shouldn't sleep for long because we need to check command queue, etc. + sleep_time = MonitorStream::MAX_SLEEP; + } else { + Debug(3, "Sleeping for %" PRIi64 " us", + static_cast(std::chrono::duration_cast(sleep_time).count())); + } + 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; } - } // 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 @@ -826,17 +906,23 @@ void MonitorStream::SingleImage(int scale) { int img_buffer_size = 0; static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE]; Image scaled_image; - Monitor::Snapshot *snap = monitor->getSnapshot(); - Image *snap_image = snap->image; + while ((monitor->shared_data->last_write_index >= monitor->image_buffer_count) and !zm_terminate) { + Debug(1, "Waiting for capture to begin"); + 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); + Image *snap_image = monitor->image_buffer[index]; + if (!config.timestamp_on_capture) { + monitor->TimestampImage(snap_image, + SystemTimePoint(zm::chrono::duration_cast(monitor->shared_timestamps[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); - } snap_image->EncodeJpeg(img_buffer, &img_buffer_size); fprintf(stdout, @@ -844,11 +930,11 @@ void MonitorStream::SingleImage(int scale) { "Content-Type: image/jpeg\r\n\r\n", img_buffer_size); fwrite(img_buffer, img_buffer_size, 1, stdout); -} +} // end void MonitorStream::SingleImage(int scale) void MonitorStream::SingleImageRaw(int scale) { Image scaled_image; - Monitor::Snapshot *snap = monitor->getSnapshot(); + ZMPacket *snap = monitor->getSnapshot(); Image *snap_image = snap->image; if ( scale != ZM_SCALE_BASE ) { @@ -861,11 +947,11 @@ void MonitorStream::SingleImageRaw(int scale) { } fprintf(stdout, - "Content-Length: %d\r\n" + "Content-Length: %u\r\n" "Content-Type: image/x-rgb\r\n\r\n", snap_image->Size()); fwrite(snap_image->Buffer(), snap_image->Size(), 1, stdout); -} +} // end void MonitorStream::SingleImageRaw(int scale) #ifdef HAVE_ZLIB_H void MonitorStream::SingleImageZip(int scale) { @@ -873,7 +959,7 @@ void MonitorStream::SingleImageZip(int scale) { static Bytef img_buffer[ZM_MAX_IMAGE_SIZE]; Image scaled_image; - Monitor::Snapshot *snap = monitor->getSnapshot(); + ZMPacket *snap = monitor->getSnapshot(); Image *snap_image = snap->image; if ( scale != ZM_SCALE_BASE ) { @@ -887,9 +973,9 @@ void MonitorStream::SingleImageZip(int scale) { snap_image->Zip(img_buffer, &img_buffer_size); fprintf(stdout, - "Content-Length: %ld\r\n" + "Content-Length: %lu\r\n" "Content-Type: image/x-rgbz\r\n\r\n", img_buffer_size); fwrite(img_buffer, img_buffer_size, 1, stdout); -} +} // end void MonitorStream::SingleImageZip(int scale) #endif // HAVE_ZLIB_H diff --git a/src/zm_monitorstream.h b/src/zm_monitorstream.h index e120331af..04d2be25c 100644 --- a/src/zm_monitorstream.h +++ b/src/zm_monitorstream.h @@ -20,19 +20,15 @@ #ifndef ZM_MONITORSTREAM_H #define ZM_MONITORSTREAM_H -#include "zm.h" -#include "zm_coord.h" -#include "zm_image.h" -#include "zm_utils.h" -#include "zm_monitor.h" +#include "zm_stream.h" 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; @@ -41,39 +37,42 @@ 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 ); - void processCommand( const CmdMsg *msg ); - void SingleImage( int scale=100 ); - void SingleImageRaw( int scale=100 ); + bool checkSwapPath(const char *path, bool create_path); + 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); #ifdef HAVE_ZLIB_H - void SingleImageZip( int scale=100 ); + void SingleImageZip(int scale=100); #endif public: - MonitorStream() : - temp_image_buffer(NULL), temp_image_buffer_count(0), temp_read_index(0), temp_write_index(0), - ttl(0), playback_buffer(0), delayed(false), frame_count(0) { - } - void setStreamBuffer( int p_playback_buffer ) { + 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) + {} + + void setStreamBuffer(int p_playback_buffer) { playback_buffer = p_playback_buffer; } - void setStreamTTL( time_t p_ttl ) { - ttl = p_ttl; + void setStreamTTL(time_t p_ttl) { + ttl = Seconds(p_ttl); } - bool setStreamStart( int monitor_id ) { - return loadMonitor( monitor_id ); + bool setStreamStart(int monitor_id) { + return loadMonitor(monitor_id); } - void runStream(); + void runStream() override; }; #endif // ZM_MONITORSTREAM_H diff --git a/src/zm_mpeg.cpp b/src/zm_mpeg.cpp index 5cbea451d..275e36628 100644 --- a/src/zm_mpeg.cpp +++ b/src/zm_mpeg.cpp @@ -1,34 +1,27 @@ /* * ZoneMinder MPEG 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 -#include - -#include "zm.h" -#include "zm_rgb.h" #include "zm_mpeg.h" -#if HAVE_LIBAVCODEC -extern "C" { -#include -#include -} +#include "zm_logger.h" +#include "zm_rgb.h" +#include "zm_time.h" bool VideoStream::initialised = false; @@ -40,88 +33,35 @@ VideoStream::MimeData VideoStream::mime_data[] = { }; void VideoStream::Initialise( ) { - if ( logDebugging() ) { - av_log_set_level( AV_LOG_DEBUG ); - } else { - av_log_set_level( AV_LOG_QUIET ); - } - - av_register_all( ); -#if LIBAVFORMAT_VERSION_CHECK(53, 13, 0, 19, 0) - avformat_network_init(); -#endif + FFMPEGInit(); initialised = true; } void VideoStream::SetupFormat( ) { /* allocate the output media context */ - ofc = NULL; -#if (LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 2, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) - avformat_alloc_output_context2( &ofc, NULL, 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; - } + ofc = nullptr; + avformat_alloc_output_context2(&ofc, nullptr, format, filename); - AVOutputFormat *oformat; - if (format) { -#if LIBAVFORMAT_VERSION_CHECK(52, 45, 0, 45, 0) - oformat = av_guess_format(format, NULL, NULL); -#else - oformat = guess_format(format, NULL, NULL); -#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(NULL, filename, NULL); -#else - oformat = guess_format(NULL, filename, NULL); -#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 = NULL; - } - - if ( filename ) { - snprintf( s->filename, sizeof(s->filename), "%s", filename ); - } - - ofc = s; -#endif - if ( !ofc ) { - Fatal( "avformat_alloc_..._context failed: %d", ofc ); + if (!ofc) { + Fatal("avformat_alloc_..._context failed"); } of = ofc->oformat; - Debug( 1, "Using output format: %s (%s)", of->name, of->long_name ); + Debug(1, "Using output format: %s (%s)", of->name, of->long_name); } -void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int height, int bitrate, double frame_rate ) { +int VideoStream::SetupCodec( + int colours, + int subpixelorder, + int width, + int height, + int bitrate, + double frame_rate + ) { /* ffmpeg format matching */ - switch(colours) { + switch (colours) { case ZM_COLOUR_RGB24: - if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { + if (subpixelorder == ZM_SUBPIX_ORDER_BGR) { /* BGR subpixel order */ pf = AV_PIX_FMT_BGR24; } else { @@ -130,13 +70,13 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei } break; case ZM_COLOUR_RGB32: - if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + if (subpixelorder == ZM_SUBPIX_ORDER_ARGB) { /* ARGB subpixel order */ pf = AV_PIX_FMT_ARGB; - } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + } else if (subpixelorder == ZM_SUBPIX_ORDER_ABGR) { /* ABGR subpixel order */ pf = AV_PIX_FMT_ABGR; - } else if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + } else if (subpixelorder == ZM_SUBPIX_ORDER_BGRA) { /* BGRA subpixel order */ pf = AV_PIX_FMT_BGRA; } else { @@ -152,85 +92,58 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei break; } - if ( strcmp( "rtp", of->name ) == 0 ) { + if (strcmp("rtp", of->name) == 0) { // RTP must have a packet_size. // Not sure what this value should be really... ofc->packet_size = width*height; Debug(1,"Setting packet_size to %d", ofc->packet_size); - - if ( of->video_codec == AV_CODEC_ID_NONE ) { + + if (of->video_codec == AV_CODEC_ID_NONE) { // RTP does not have a default codec in ffmpeg <= 0.8. of->video_codec = AV_CODEC_ID_MPEG4; } } - + _AVCODECID codec_id = of->video_codec; - if ( codec_name ) { + if (codec_name) { AVCodec *a = avcodec_find_encoder_by_name(codec_name); - if ( a ) { + if (a) { codec_id = a->id; - Debug( 1, "Using codec \"%s\"", codec_name ); + 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 + Debug(1, "Could not find codec \"%s\". Using default \"%s\"", codec_name, avcodec_get_name(codec_id)); } } /* add the video streams using the default format codecs and initialize the codecs */ - ost = NULL; - 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 + ost = nullptr; + if (codec_id != AV_CODEC_ID_NONE) { + codec = avcodec_find_encoder(codec_id); + if (!codec) { + Error("Could not find encoder for '%s'", avcodec_get_name(codec_id)); + return -1; } -#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" ); - return; + Debug(1, "Found encoder for '%s'", avcodec_get_name(codec_id)); + ost = avformat_new_stream(ofc, codec); + if (!ost) { + Error("Could not alloc stream"); + return -1; } - Debug( 1, "Allocated stream (%d) !=? (%d)", ost->id , ofc->nb_streams - 1 ); + 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(NULL); + 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; codec_context->pix_fmt = strcmp("mjpeg", ofc->oformat->name) == 0 ? AV_PIX_FMT_YUVJ422P : AV_PIX_FMT_YUV420P; - if ( bitrate <= 100 ) { + 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; @@ -247,48 +160,42 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei codec_context->time_base.num = 1; ost->time_base.den = frame_rate; ost->time_base.num = 1; - - Debug( 1, "Will encode in %d fps. %dx%d", codec_context->time_base.den, width, height ); - + /* emit one intra frame every second */ codec_context->gop_size = frame_rate; // some formats want stream headers to be separate - if ( of->flags & AVFMT_GLOBALHEADER ) -#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) + if (of->flags & AVFMT_GLOBALHEADER) 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" ); - } + Error("of->video_codec == AV_CODEC_ID_NONE"); + return -1; + } + return 0; } void VideoStream::SetParameters( ) { } -const char *VideoStream::MimeType( ) const { +const char *VideoStream::MimeType() const { for ( unsigned int i = 0; i < sizeof (mime_data) / sizeof (*mime_data); i++ ) { - if ( strcmp( format, mime_data[i].format ) == 0 ) { - Debug( 1, "MimeType is \"%s\"", mime_data[i].mime_type ); + if ( strcmp(format, mime_data[i].format) == 0 ) { + Debug(1, "MimeType is \"%s\"", mime_data[i].mime_type); return mime_data[i].mime_type; } } const char *mime_type = of->mime_type; if ( !mime_type ) { - std::string mime = "video/"; - mime = mime.append( format ); - mime_type = mime.c_str( ); - Warning( "Unable to determine mime type for '%s' format, using '%s' as default", format, mime_type ); + std::string mime = std::string("video/") + format; + mime_type = strdup(mime.c_str()); // mem leak + Warning( "Unable to determine mime type for '%s' format, using '%s' as default", format, mime_type); } - Debug(1, "MimeType is \"%s\"", mime_type ); + Debug(1, "MimeType is \"%s\"", mime_type); return mime_type; } @@ -296,18 +203,14 @@ const char *VideoStream::MimeType( ) const { bool VideoStream::OpenStream( ) { int ret; - /* now that all the parameters are set, we can open the + /* now that all the parameters are set, we can open the video codecs and allocate the necessary encode buffers */ if ( ost ) { 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, 0)) < 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; } @@ -324,11 +227,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 ) { @@ -336,59 +235,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 = NULL; + 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, NULL, NULL ); -#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 + ret = avio_open2( &ofc->pb, filename, AVIO_FLAG_WRITE, nullptr, nullptr ); + if ( ret < 0 ) { Error("Could not open '%s'", filename); return false; @@ -400,34 +279,22 @@ bool VideoStream::OpenStream( ) { return false; } - video_outbuf = NULL; -#if LIBAVFORMAT_VERSION_CHECK(57, 0, 0, 0, 0) + video_outbuf = nullptr; 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. video_outbuf_size = 4000000; video_outbuf = (uint8_t *)malloc( video_outbuf_size ); - if ( video_outbuf == NULL ) { + if ( video_outbuf == nullptr ) { Fatal("Unable to malloc memory for outbuf"); } } -#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, NULL); -#endif + ret = avformat_write_header(ofc, nullptr); if ( ret < 0 ) { Error("?_write_header failed with error %d \"%s\"", ret, av_err2str(ret)); @@ -439,16 +306,16 @@ bool VideoStream::OpenStream( ) { VideoStream::VideoStream( const char *in_filename, const char *in_format, int bitrate, double frame_rate, int colours, int subpixelorder, int width, int height ) : filename(in_filename), format(in_format), - opicture(NULL), - tmp_opicture(NULL), - video_outbuf(NULL), + opicture(nullptr), + tmp_opicture(nullptr), + video_outbuf(nullptr), video_outbuf_size(0), last_pts( -1 ), streaming_thread(0), do_streaming(true), add_timestamp(false), timestamp(0), - buffer_copy(NULL), + buffer_copy(nullptr), buffer_copy_lock(new pthread_mutex_t), buffer_copy_size(0), buffer_copy_used(0), @@ -457,33 +324,33 @@ VideoStream::VideoStream( const char *in_filename, const char *in_format, int bi if ( !initialised ) { Initialise( ); } - + if ( format ) { int length = strlen(format); codec_and_format = new char[length+1];; strcpy( codec_and_format, format ); format = codec_and_format; - codec_name = NULL; + codec_name = nullptr; char *f = strchr(codec_and_format, '/'); - if (f != NULL) { + if (f != nullptr) { *f = 0; codec_name = f+1; } } - codec_context = NULL; + codec_context = nullptr; SetupFormat( ); SetupCodec( colours, subpixelorder, width, height, bitrate, frame_rate ); SetParameters( ); - + // Allocate buffered packets. packet_buffers = new AVPacket*[2]; packet_buffers[0] = new AVPacket(); packet_buffers[1] = new AVPacket(); packet_index = 0; - + // Initialize mutex used by streaming thread. - if ( pthread_mutex_init( buffer_copy_lock, NULL ) != 0 ) { + if ( pthread_mutex_init( buffer_copy_lock, nullptr ) != 0 ) { Fatal("pthread_mutex_init failed"); } @@ -491,35 +358,35 @@ VideoStream::VideoStream( const char *in_filename, const char *in_format, int bi VideoStream::~VideoStream( ) { Debug( 1, "VideoStream destructor." ); - + // Stop streaming thread. if ( streaming_thread ) { do_streaming = false; void* thread_exit_code; - + Debug( 1, "Asking streaming thread to exit." ); - + // Wait for thread to exit. pthread_join(streaming_thread, &thread_exit_code); } - - if ( buffer_copy != NULL ) { + + if ( buffer_copy != nullptr ) { av_free( buffer_copy ); } - + if ( buffer_copy_lock ) { if ( pthread_mutex_destroy( buffer_copy_lock ) != 0 ) { Error( "pthread_mutex_destroy failed" ); } delete buffer_copy_lock; } - + if (packet_buffers) { delete packet_buffers[0]; delete packet_buffers[1]; delete[] packet_buffers; } - + /* close each codec */ if ( ost ) { avcodec_close( codec_context ); @@ -542,16 +409,12 @@ 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 */ av_free( ofc ); - + /* free format and codec_name data. */ if ( codec_and_format ) { delete codec_and_format; @@ -562,12 +425,12 @@ double VideoStream::EncodeFrame( const uint8_t *buffer, int buffer_size, bool _a if ( pthread_mutex_lock(buffer_copy_lock) != 0 ) { Fatal( "EncodeFrame: pthread_mutex_lock failed." ); } - + if (buffer_copy_size < buffer_size) { if ( buffer_copy ) { av_free(buffer_copy); } - + // Allocate a buffer to store source images for the streaming thread to encode. buffer_copy = (uint8_t *)av_malloc(buffer_size); if ( !buffer_copy ) { @@ -577,110 +440,75 @@ double VideoStream::EncodeFrame( const uint8_t *buffer, int buffer_size, bool _a } buffer_copy_size = buffer_size; } - + add_timestamp = _add_timestamp; timestamp = _timestamp; buffer_copy_used = buffer_size; memcpy(buffer_copy, buffer, buffer_size); - + if ( pthread_mutex_unlock(buffer_copy_lock) != 0 ) { Fatal( "EncodeFrame: pthread_mutex_unlock failed." ); } - + if ( streaming_thread == 0 ) { Debug( 1, "Starting streaming thread" ); - + // Start a thread for streaming encoded video. - if (pthread_create( &streaming_thread, NULL, StreamingThreadCallback, (void*) this) != 0){ + if (pthread_create( &streaming_thread, nullptr, StreamingThreadCallback, (void*) this) != 0){ // Log a fatal error and exit the process. Fatal( "VideoStream failed to create streaming thread." ); } } - + //return ActuallyEncodeFrame( buffer, buffer_size, add_timestamp, timestamp); - + return _timestamp; } 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 = 0; -#endif // HAVE_LIBSWSCALE + static struct SwsContext *img_convert_ctx = nullptr; memcpy( tmp_opicture->data[0], buffer, buffer_size ); -#ifdef HAVE_LIBSWSCALE if ( !img_convert_ctx ) { - img_convert_ctx = sws_getCachedContext( NULL, codec_context->width, codec_context->height, pf, codec_context->width, codec_context->height, codec_context->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL ); + 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 ); } AVFrame *opicture_ptr = opicture; - + AVPacket *pkt = packet_buffers[packet_index]; av_init_packet( pkt ); int got_packet = 0; -#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 - -#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); + if (codec_context->codec_type == AVMEDIA_TYPE_VIDEO && + codec_context->codec_id == AV_CODEC_ID_RAWVIDEO) { + pkt->flags |= AV_PKT_FLAG_KEY; + pkt->stream_index = ost->index; + pkt->data = (uint8_t *)opicture_ptr; + pkt->size = sizeof (AVPicture); 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 ) { - if ( AVERROR_EOF != ret ) { - Error("ERror encoding video (%d) (%s)", ret, - av_err2str(ret)); - } - } else { - got_packet = 1; + avcodec_send_frame(codec_context, opicture_ptr); + int ret = avcodec_receive_packet(codec_context, pkt); + if (ret < 0) { + if (AVERROR_EOF != ret) { + Error("ERror encoding video (%d) (%s)", ret, av_err2str(ret)); } -#else + } else { + got_packet = 1; + } -#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 : NULL; - 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 (got_packet) { + // if ( c->coded_frame->key_frame ) + // { + // pkt->flags |= AV_PKT_FLAG_KEY; + // } if ( pkt->pts != (int64_t)AV_NOPTS_VALUE ) { pkt->pts = av_rescale_q( pkt->pts, codec_context->time_base, ost->time_base ); @@ -692,53 +520,42 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, pkt->stream_index = ost->index; } } - - return ( opicture_ptr->pts); + + return opicture_ptr->pts; } int VideoStream::SendPacket(AVPacket *packet) { - - int ret = av_write_frame( ofc, 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; + int ret = av_write_frame(ofc, packet); + if (ret < 0) { + Error("Error %d while writing video frame: %s", ret, av_err2str(errno)); + } + av_packet_unref(packet); + return ret; } -void *VideoStream::StreamingThreadCallback(void *ctx){ - - Debug( 1, "StreamingThreadCallback started" ); - - if (ctx == NULL) return NULL; +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. @@ -749,32 +566,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 0; -} + 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 6e75f8743..552652a1d 100644 --- a/src/zm_mpeg.h +++ b/src/zm_mpeg.h @@ -21,8 +21,7 @@ #define ZM_MPEG_H #include "zm_ffmpeg.h" - -#if HAVE_LIBAVCODEC +#include class VideoStream { protected: @@ -69,7 +68,7 @@ protected: static void Initialise(); void SetupFormat( ); - void SetupCodec( int colours, int subpixelorder, int width, int height, int bitrate, double frame_rate ); + int SetupCodec( int colours, int subpixelorder, int width, int height, int bitrate, double frame_rate ); void SetParameters(); void ActuallyOpenStream(); double ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, bool add_timestamp=false, unsigned int timestamp=0 ); @@ -82,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 a04282d26..55a78721e 100644 --- a/src/zm_packet.cpp +++ b/src/zm_packet.cpp @@ -16,35 +16,269 @@ //You should have received a copy of the GNU General Public License //along with ZoneMinder. If not, see . - #include "zm_packet.h" -#include "zm_ffmpeg.h" -#include +#include "zm_ffmpeg.h" +#include "zm_image.h" +#include "zm_logger.h" using namespace std; +AVPixelFormat target_format = AV_PIX_FMT_NONE; -ZMPacket::ZMPacket( AVPacket *p ) { - frame = NULL; - image = NULL; - av_init_packet( &packet ); - if ( zm_av_packet_ref( &packet, p ) < 0 ) { - Error("error refing packet"); - } - gettimeofday( ×tamp, NULL ); +ZMPacket::ZMPacket() : + keyframe(0), + stream(nullptr), + in_frame(nullptr), + out_frame(nullptr), + buffer(nullptr), + image(nullptr), + 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. } -ZMPacket::ZMPacket( AVPacket *p, struct timeval *t ) { - frame = NULL; - image = NULL; - av_init_packet( &packet ); - if ( zm_av_packet_ref( &packet, p ) < 0 ) { +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. +} + +ZMPacket::ZMPacket(ZMPacket &p) : + keyframe(0), + stream(nullptr), + in_frame(nullptr), + out_frame(nullptr), + timestamp(p.timestamp), + buffer(nullptr), + image(nullptr), + 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; + packet.data = nullptr; + if (zm_av_packet_ref(&packet, &p.packet) < 0) { Error("error refing packet"); - } - timestamp = *t; + } } ZMPacket::~ZMPacket() { - zm_av_packet_unref( &packet ); + zm_av_packet_unref(&packet); + if (in_frame) av_frame_free(&in_frame); + if (out_frame) av_frame_free(&out_frame); + if (buffer) av_freep(&buffer); + delete analysis_image; + delete image; } +/* returns < 0 on error, 0 on not ready, int bytes consumed on success + * This functions job is to populate in_frame with the image in an appropriate + * format. It MAY also populate image if able to. In this case in_frame is populated + * by the image buffer. + */ +int ZMPacket::decode(AVCodecContext *ctx) { + Debug(4, "about to decode video, image_index is (%d)", image_index); + + if (in_frame) { + Error("Already have a frame?"); + } else { + in_frame = zm_av_frame_alloc(); + } + + // packets are always stored in AV_TIME_BASE_Q so need to convert to codec time base + //av_packet_rescale_ts(&packet, AV_TIME_BASE_Q, ctx->time_base); + + int ret = zm_send_packet_receive_frame(ctx, in_frame, packet); + if (ret < 0) { + if (AVERROR(EAGAIN) != ret) { + Warning("Unable to receive frame : code %d %s.", + ret, av_make_error_string(ret).c_str()); + } + av_frame_free(&in_frame); + return 0; + } + int bytes_consumed = ret; + if (ret > 0) { + zm_dump_video_frame(in_frame, "got frame"); + +#if HAVE_LIBAVUTIL_HWCONTEXT_H +#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) + + if (fix_deprecated_pix_fmt(ctx->sw_pix_fmt) != fix_deprecated_pix_fmt(static_cast(in_frame->format))) { + Debug(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)) + ); +#if 0 + if ( target_format == AV_PIX_FMT_NONE and ctx->hw_frames_ctx and (image->Colours() == 4) ) { + // Look for rgb0 in list of supported formats + enum AVPixelFormat *formats; + if ( 0 <= av_hwframe_transfer_get_formats( + ctx->hw_frames_ctx, + AV_HWFRAME_TRANSFER_DIRECTION_FROM, + &formats, + 0 + ) ) { + for (int i = 0; formats[i] != AV_PIX_FMT_NONE; i++) { + Debug(1, "Available dest formats %d %s", + formats[i], + av_get_pix_fmt_name(formats[i]) + ); + if ( formats[i] == AV_PIX_FMT_RGB0 ) { + target_format = formats[i]; + break; + } // endif RGB0 + } // end foreach support format + av_freep(&formats); + } // endif success getting list of formats + } // end if target_format not set +#endif + + AVFrame *new_frame = zm_av_frame_alloc(); +#if 0 + if ( target_format == AV_PIX_FMT_RGB0 ) { + if ( image ) { + if ( 0 > image->PopulateFrame(new_frame) ) { + delete new_frame; + new_frame = zm_av_frame_alloc(); + delete image; + image = nullptr; + new_frame->format = target_format; + } + } + } +#endif + /* retrieve data from GPU to CPU */ + zm_dump_video_frame(in_frame, "Before hwtransfer"); + ret = av_hwframe_transfer_data(new_frame, in_frame, 0); + if (ret < 0) { + Error("Unable to transfer frame: %s, continuing", + av_make_error_string(ret).c_str()); + av_frame_free(&in_frame); + av_frame_free(&new_frame); + return 0; + } + ret = av_frame_copy_props(new_frame, in_frame); + if (ret < 0) { + Error("Unable to copy props: %s, continuing", + av_make_error_string(ret).c_str()); + } + + zm_dump_video_frame(new_frame, "After hwtransfer"); +#if 0 + if ( new_frame->format == AV_PIX_FMT_RGB0 ) { + new_frame->format = AV_PIX_FMT_RGBA; + zm_dump_video_frame(new_frame, "After hwtransfer setting to rgba"); + } +#endif + av_frame_free(&in_frame); + in_frame = new_frame; + } else +#endif +#endif + Debug(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) + ); +#if 0 + if ( image ) { + image->Assign(in_frame); + } +#endif + } // end if if ( ret > 0 ) { + return bytes_consumed; +} // end ZMPacket::decode + +Image *ZMPacket::get_image(Image *i) { + if (!in_frame) { + Error("Can't get image without frame.. maybe need to decode first"); + return nullptr; + } + if (!image) { + if (!i) { + Error("Need a pre-allocated image buffer"); + return nullptr; + } + image = i; + } + image->Assign(in_frame); + return image; +} + +Image *ZMPacket::set_image(Image *i) { + image = i; + return image; +} + +AVPacket *ZMPacket::set_packet(AVPacket *p) { + if (zm_av_packet_ref(&packet, p) < 0) { + Error("error refing packet"); + } + + timestamp = std::chrono::system_clock::now(); + keyframe = p->flags & AV_PKT_FLAG_KEY; + return &packet; +} + +AVFrame *ZMPacket::get_out_frame(int width, int height, AVPixelFormat format) { + if (!out_frame) { + out_frame = zm_av_frame_alloc(); + if (!out_frame) { + Error("Unable to allocate a frame"); + return nullptr; + } + + int alignment = 32; + if (width%alignment) alignment = 1; + + codec_imgsize = av_image_get_buffer_size( + 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); + int ret; + if ((ret=av_image_fill_arrays( + out_frame->data, + out_frame->linesize, + buffer, + 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 bdb67cb57..8a6a31de5 100644 --- a/src/zm_packet.h +++ b/src/zm_packet.h @@ -20,27 +20,104 @@ #ifndef ZM_PACKET_H #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__ -#include "zm_image.h" +class Image; class ZMPacket { public: - AVPacket packet; // Input packet, undecoded - AVFrame *frame; // Input image, decoded - Image *image; // Our internal image oject representing this frame - struct timeval timestamp; + std::mutex mutex_; + // 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. + SystemTimePoint timestamp; + uint8_t *buffer; // buffer used in image + Image *image; + Image *analysis_image; + int score; + AVMediaType codec_type; + int image_index; + int codec_imgsize; + int64_t pts; // pts in the packet can be in another time base. This MUST be in AV_TIME_BASE_Q + bool decoded; + std::vector zone_stats; + std::string alarm_cause; + public: AVPacket *av_packet() { return &packet; } - ZMPacket( AVPacket *packet, struct timeval *timestamp ); - explicit ZMPacket( AVPacket *packet ); + AVPacket *set_packet(AVPacket *p) ; + AVFrame *av_frame() { return out_frame; } + Image *get_image(Image *i=nullptr); + Image *set_image(Image *); + + int is_keyframe() { return keyframe; }; + int decode(AVCodecContext *ctx); + explicit ZMPacket(Image *image, SystemTimePoint tv); + explicit ZMPacket(ZMPacket &packet); + ZMPacket(); ~ZMPacket(); + + //AVFrame *get_out_frame(const AVCodecContext *ctx); + AVFrame *get_out_frame(int width, int height, AVPixelFormat format); + int get_codec_imgsize() { return codec_imgsize; }; +}; + +class ZMLockedPacket { + public: + 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 e3ffb8c9a..26f19de75 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -17,382 +17,655 @@ //along with ZoneMinder. If not, see . +// PacketQueue must know about all iterators and manage them + #include "zm_packetqueue.h" + #include "zm_ffmpeg.h" -#include -#include "zm_time.h" +#include "zm_packet.h" +#include "zm_signal.h" -zm_packetqueue::zm_packetqueue( int p_max_stream_id ) { - max_stream_id = p_max_stream_id; - packet_counts = new int[max_stream_id+1]; - for ( int i=0; i <= max_stream_id; ++i ) - packet_counts[i] = 0; +PacketQueue::PacketQueue(): + video_stream_id(-1), + max_video_packet_count(-1), + pre_event_video_packet_count(-1), + max_stream_id(-1), + packet_counts(nullptr), + deleting(false), + keep_keyframes(false) +{ } -zm_packetqueue::~zm_packetqueue() { - clearQueue(); - delete[] packet_counts; - packet_counts = NULL; -} - -bool zm_packetqueue::queuePacket(ZMPacket* zm_packet) { - - if ( - ( zm_packet->packet.dts == AV_NOPTS_VALUE ) - || - ( packet_counts[zm_packet->packet.stream_index] <= 0 ) - ) { - Debug(2,"Inserting packet with dts %" PRId64 " because queue %d is empty (queue size: %d) or invalid dts", - zm_packet->packet.dts, zm_packet->packet.stream_index, packet_counts[zm_packet->packet.stream_index] - ); - // No dts value, can't so much with it - pktQueue.push_back(zm_packet); - packet_counts[zm_packet->packet.stream_index] += 1; - return true; +/* Assumes queue is empty when adding streams + * Assumes first stream added will be the video stream + */ +int PacketQueue::addStream() { + deleting = false; + if (max_stream_id == -1) { + video_stream_id = 0; + max_stream_id = 0; + } else { + max_stream_id ++; } + if (packet_counts) delete[] packet_counts; + packet_counts = new int[max_stream_id+1]; + for (int i=0; i <= max_stream_id; ++i) + packet_counts[i] = 0; + return max_stream_id; +} + +PacketQueue::~PacketQueue() { + clear(); + if (packet_counts) { + delete[] packet_counts; + packet_counts = nullptr; + } + while (!iterators.empty()) { + packetqueue_iterator *it = iterators.front(); + iterators.pop_front(); + delete it; + } + Debug(4, "Done in destructor"); +} + +/* Enqueues the given packet. Will maintain the it pointer and image packet counts. + * If we have reached our max image packet count, it will pop off as many packets as are needed. + * Thus it will ensure that the same packet never gets queued twice. + */ + +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; + } + if (!packet_counts[video_stream_id] and !add_packet->keyframe) { + Debug(4, "No video keyframe so no one needs us to queue packets."); + return false; + } + { + std::unique_lock lck(mutex); + + 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 ( + 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 // can't git end because we added our packet + *it != add_packet; + // iterator is incremented by erase + ) { + std::shared_ptr zm_packet = *it; + + ZMLockedPacket lp(zm_packet); + if (!lp.trylock()) { + Warning("Found locked packet when trying to free up video packets. This basically means that decoding is not keeping up."); + ++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) == 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()); + + 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_one(); + + return true; +} // end bool PacketQueue::queuePacket(ZMPacket* zm_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 + // 2. Have minimum # of video packets + // 3. No packets can be locked + // 4. No iterator can point to one of the packets + // + // So start at the beginning, counting video packets until the next keyframe. + // Then if deleting those packets doesn't break 1 and 2, then go ahead and delete them. + if (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] > 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); + + // 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()); + + 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; + + if (is_there_an_iterator_pointing_to_packet(zm_packet)) { + Warning("Found iterator at beginning of queue. Some thread isn't keeping up"); + break; + } + + 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 - std::list::reverse_iterator it = pktQueue.rbegin(); - - // Scan through the queue looking for a packet for our stream with a dts <= ours. - while ( it != pktQueue.rend() ) { - AVPacket *av_packet = &((*it)->packet); - - Debug(2, "Looking at packet with stream index (%d) with dts %" PRId64, - av_packet->stream_index, av_packet->dts); - if ( av_packet->stream_index == zm_packet->packet.stream_index ) { - if ( - ( av_packet->dts != AV_NOPTS_VALUE ) - && - ( av_packet->dts <= zm_packet->packet.dts) - ) { - Debug(2, "break packet with stream index (%d) with dts %" PRId64, - (*it)->packet.stream_index, (*it)->packet.dts); + // 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; } - } else { // Not same stream, compare timestamps - if ( tvDiffUsec(((*it)->timestamp, zm_packet->timestamp) ) <= 0 ) { - Debug(2, "break packet with stream index (%d) with dts %" PRId64, - (*it)->packet.stream_index, (*it)->packet.dts); - break; - } - } - it++; - } // end while not the end of the queue - - if ( it != pktQueue.rend() ) { - Debug(2, "Found packet with stream index (%d) with dts %" PRId64 " <= %" PRId64, - (*it)->packet.stream_index, (*it)->packet.dts, zm_packet->packet.dts); - if ( it == pktQueue.rbegin() ) { - Debug(2,"Inserting packet with dts %" PRId64 " at end", zm_packet->packet.dts); - // No dts value, can't so much with it - pktQueue.push_back(zm_packet); - packet_counts[zm_packet->packet.stream_index] += 1; - return true; - } - // Convert to a forward iterator so that we can insert at end - std::list::iterator f_it = it.base(); - - Debug(2, "Insert packet before packet with stream index (%d) with dts %" PRId64 " for dts %" PRId64, - (*f_it)->packet.stream_index, (*f_it)->packet.dts, zm_packet->packet.dts); - - pktQueue.insert(f_it, zm_packet); - - packet_counts[zm_packet->packet.stream_index] += 1; - return true; - } - Debug(1,"Unable to insert packet for stream %d with dts %" PRId64 " into queue.", - zm_packet->packet.stream_index, zm_packet->packet.dts); #endif - pktQueue.push_back(zm_packet); - packet_counts[zm_packet->packet.stream_index] += 1; - return true; -} // end bool zm_packetqueue::queuePacket(ZMPacket* zm_packet) -bool zm_packetqueue::queuePacket(AVPacket* av_packet) { - ZMPacket *zm_packet = new ZMPacket(av_packet); - return queuePacket(zm_packet); -} + 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; + } // end while + } // end if first packet not locked + 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) { + zm_packet = *pktQueue.begin(); + if (!zm_packet) { + Error("NULL zm_packet in queue"); + continue; + } -ZMPacket* zm_packetqueue::popPacket( ) { - if ( pktQueue.empty() ) { - return NULL; - } - - ZMPacket *packet = pktQueue.front(); - pktQueue.pop_front(); - packet_counts[packet->packet.stream_index] -= 1; - - return packet; -} - -unsigned int zm_packetqueue::clearQueue(unsigned int frames_to_keep, int stream_id) { - - Debug(3, "Clearing all but %d frames, queue has %d", frames_to_keep, pktQueue.size()); - frames_to_keep += 1; - - if ( pktQueue.empty() ) { - Debug(3, "Queue is empty"); - return 0; - } - - std::list::reverse_iterator it; - ZMPacket *packet = NULL; - - for ( it = pktQueue.rbegin(); it != pktQueue.rend() && frames_to_keep; ++it ) { - ZMPacket *zm_packet = *it; - AVPacket *av_packet = &(zm_packet->packet); - - Debug(4, "Looking at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", - av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep ); - - // Want frames_to_keep video keyframes. Otherwise, we may not have enough - if ( ( av_packet->stream_index == stream_id) ) { - //&& ( av_packet->flags & AV_PKT_FLAG_KEY ) ) { - frames_to_keep --; + 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; } - } + } // end if have at least max_video_packet_count video packets remaining + // We signal on every packet because someday we may analyze sound - // Make sure we start on a keyframe - for ( ; it != pktQueue.rend(); ++it ) { - ZMPacket *zm_packet = *it; - AVPacket *av_packet = &(zm_packet->packet); - - Debug(5, "Looking for keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", - av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep); - - // Want frames_to_keep video keyframes. Otherwise, we may not have enough - if ( (av_packet->stream_index == stream_id) && (av_packet->flags & AV_PKT_FLAG_KEY) ) { - Debug(4, "Found keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", - av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep); - break; - } - } - if ( frames_to_keep ) { - Debug(3, "Hit end of queue, still need (%d) video frames", frames_to_keep); - } - if ( it != pktQueue.rend() ) { - // We want to keep this packet, so advance to the next - ++it; - } - unsigned int delete_count = 0; - while ( it != pktQueue.rend() ) { - Debug(4, "Deleting a packet from the front, count is (%d)", delete_count); + return; +} // end voidPacketQueue::clearPackets(ZMPacket* zm_packet) - packet = pktQueue.front(); +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()) { + 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 + 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_counts[packet->packet.stream_index] -= 1; - delete packet; + delete lp; + //delete packet; + } + Debug(1, "Packetqueue is clear, deleting iterators"); - delete_count += 1; - } - packet = NULL; // tidy up for valgrind - Debug(3, "Deleted %d packets, %d remaining", delete_count, pktQueue.size()); - return delete_count; -} // end unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id ) + for ( + std::list::iterator iterators_it = iterators.begin(); + iterators_it != iterators.end(); + ++iterators_it + ) { + packetqueue_iterator *iterator_it = *iterators_it; + *iterator_it = pktQueue.begin(); + } // end foreach iterator -void zm_packetqueue::clearQueue() { - ZMPacket *packet = NULL; - int delete_count = 0; - while ( !pktQueue.empty() ) { - packet = pktQueue.front(); - packet_counts[packet->packet.stream_index] -= 1; - pktQueue.pop_front(); - delete packet; - delete_count += 1; - } - Debug(3, "Deleted (%d) packets", delete_count ); + 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 zm_packetqueue::clearQueue(struct timeval *duration, int streamId) { - - if (pktQueue.empty()) { - return 0; - } - struct timeval keep_from; - std::list::reverse_iterator it; - it = pktQueue.rbegin(); - - timersub(&(*it)->timestamp, 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 - && 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"); - 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 - && 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" ); - return 0; - } - - unsigned int deleted_frames = 0; - ZMPacket *zm_packet = NULL; - while (distance(it, pktQueue.rend()) > 1) { - zm_packet = pktQueue.front(); - pktQueue.pop_front(); - packet_counts[zm_packet->packet.stream_index] -= 1; - delete zm_packet; - deleted_frames += 1; - } - zm_packet = NULL; - Debug(3, "Deleted %d frames", deleted_frames); - - return deleted_frames; -} - -unsigned int zm_packetqueue::size() { +unsigned int PacketQueue::size() { return pktQueue.size(); } -int zm_packetqueue::packet_count( int stream_id ) { +int PacketQueue::packet_count(int stream_id) { + if (stream_id < 0 or stream_id > max_stream_id) { + Error("Invalid stream_id %d max is %d", stream_id, max_stream_id); + return -1; + } return packet_counts[stream_id]; -} // end int zm_packetqueue::packet_count( int stream_id ) +} // end int PacketQueue::packet_count(int stream_id) -// Clear packets before the given timestamp. -// Must also take into account pre_event_count frames -void zm_packetqueue::clear_unwanted_packets( - timeval *recording_started, - int pre_event_count, - int mVideoStreamId) { - // Need to find the keyframe <= recording_started. Can get rid of audio packets. - if ( pktQueue.empty() ) - return; +// Returns a packet. Packet will be locked +ZMLockedPacket *PacketQueue::get_packet(packetqueue_iterator *it) { + if (deleting or zm_terminate) + return nullptr; - // Step 1 - find frame <= recording_started. - // Step 2 - go back pre_event_count - // Step 3 - find a keyframe - // Step 4 - pop packets until we get to the packet in step 3 - std::list::reverse_iterator it; + Debug(4, "Locking in get_packet using it %p queue end? %d", + std::addressof(*it), (*it == pktQueue.end())); - // Step 1 - find frame <= recording_started. - Debug(3, "Looking for frame before start (%d.%d) recording stream id (%d), queue has %d packets", - recording_started->tv_sec, recording_started->tv_usec, mVideoStreamId, pktQueue.size()); + 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 +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", + 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, 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(); + } + 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 %zu, end? %d", it, pktQueue.size(), ((*it) == pktQueue.end())); + if (((*it) == pktQueue.end()) or deleting) { + return false; + } + std::unique_lock lck(mutex); + ++(*it); + if (*it != pktQueue.end()) { + Debug(2, "Incrementing %p, %p still not at end, so returning true", it, std::addressof(*it)); + return true; + } + Debug(2, "At end"); + return false; +} // end bool PacketQueue::increment_it(packetqueue_iterator *it) + +// Increment it only considering packets for a given stream +bool PacketQueue::increment_it(packetqueue_iterator *it, int stream_id) { + Debug(2, "Incrementing %p, queue size %zu, end? %d", it, pktQueue.size(), (*it == pktQueue.end())); + if (*it == pktQueue.end()) { + return false; + } + + std::unique_lock lck(mutex); + do { + ++(*it); + } while ( (*it != pktQueue.end()) and ( (*(*it))->packet.stream_index != stream_id) ); + + if ( *it != pktQueue.end() ) { + Debug(2, "Incrementing %p, still not at end, so incrementing", it); + return true; + } + return false; +} // end bool PacketQueue::increment_it(packetqueue_iterator *it) + +packetqueue_iterator *PacketQueue::get_event_start_packet_it( + packetqueue_iterator snapshot_it, + unsigned int pre_event_count + ) { + std::unique_lock lck(mutex); + + packetqueue_iterator *it = new packetqueue_iterator; + iterators.push_back(it); + + *it = snapshot_it; + 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 + 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)--; + } + packet = *(*it); + } + // it either points to beginning or we have seen pre_event_count video packets. + + 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; + } + + 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 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; for ( it = pktQueue.rbegin(); it != pktQueue.rend(); ++ it ) { - ZMPacket *zm_packet = *it; - AVPacket *av_packet = &(zm_packet->packet); - if ( - ( av_packet->stream_index == mVideoStreamId ) - && - timercmp( &(zm_packet->timestamp), recording_started, <= ) - ) { - Debug(3, "Found frame before start with stream index %d at %d.%d", - av_packet->stream_index, - zm_packet->timestamp.tv_sec, - zm_packet->timestamp.tv_usec); - break; - } - Debug(3, "Not Found frame before start with stream index %d at %d.%d", - av_packet->stream_index, - zm_packet->timestamp.tv_sec, - zm_packet->timestamp.tv_usec); - } - - if ( it == pktQueue.rend() ) { - Info("Didn't find a frame before event starttime. keeping all"); - return; - } - - Debug(1, "Seeking back %d frames", pre_event_count); - for ( ; pre_event_count && (it != pktQueue.rend()); ++ it ) { - ZMPacket *zm_packet = *it; - AVPacket *av_packet = &(zm_packet->packet); - if ( av_packet->stream_index == mVideoStreamId ) { - --pre_event_count; - } - } - - if ( it == pktQueue.rend() ) { - Debug(1, "ran out of pre_event frames before event starttime. keeping all"); - return; - } - - Debug(3, "Looking for keyframe"); - for ( ; it != pktQueue.rend(); ++ it ) { - ZMPacket *zm_packet = *it; - AVPacket *av_packet = &(zm_packet->packet); - if ( - ( av_packet->flags & AV_PKT_FLAG_KEY ) - && - ( av_packet->stream_index == mVideoStreamId ) - ) { - Debug(3, "Found keyframe before start with stream index %d at %d.%d", - av_packet->stream_index, - zm_packet->timestamp.tv_sec, - zm_packet->timestamp.tv_usec ); - break; - } - } - if ( it == pktQueue.rend() ) { - Debug(1, "Didn't find a keyframe before event starttime. keeping all" ); - return; - } - - ZMPacket *zm_packet = *it; - AVPacket *av_packet = &(zm_packet->packet); - Debug(3, "Found packet before start with stream index (%d) with keyframe (%d), distance(%d), size(%d)", - av_packet->stream_index, - ( av_packet->flags & AV_PKT_FLAG_KEY ), - distance( it, pktQueue.rend() ), - pktQueue.size() ); - - unsigned int deleted_frames = 0; - ZMPacket *packet = NULL; - while ( distance(it, pktQueue.rend()) > 1 ) { - //while ( pktQueue.rend() != it ) { - packet = pktQueue.front(); - pktQueue.pop_front(); - packet_counts[packet->packet.stream_index] -= 1; - delete packet; - deleted_frames += 1; - } - packet = NULL; // tidy up for valgrind - - zm_packet = pktQueue.front(); - av_packet = &(zm_packet->packet); - if ( ( ! ( av_packet->flags & AV_PKT_FLAG_KEY ) ) || ( av_packet->stream_index != mVideoStreamId ) ) { - Error( "Done looking for keyframe. Deleted %d frames. Remaining frames in queue: %d stream of head packet is (%d), keyframe (%d), distance(%d), packets(%d)", - deleted_frames, pktQueue.size(), av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), distance( it, pktQueue.rend() ), pktQueue.size() ); - } else { - Debug(1, "Done looking for keyframe. Deleted %d frames. Remaining frames in queue: %d stream of head packet is (%d), keyframe (%d), distance(%d), packets(%d)", - deleted_frames, pktQueue.size(), av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), distance( it, pktQueue.rend() ), pktQueue.size() ); - } -} // end void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVideoStreamId ) - -void zm_packetqueue::dumpQueue() { - std::list::reverse_iterator it; - for ( it = pktQueue.rbegin(); it != pktQueue.rend(); ++ it ) { - ZMPacket *zm_packet = *it; - AVPacket *av_packet = &(zm_packet->packet); - dumpPacket(av_packet); + std::shared_ptr zm_packet = *it; + ZM_DUMP_PACKET(zm_packet->packet, ""); } } + +/* Returns an iterator to the first video keyframe in the queue. + * nullptr if no keyframe video packet exists. + */ +packetqueue_iterator * PacketQueue::get_video_it(bool wait) { + packetqueue_iterator *it = new packetqueue_iterator; + iterators.push_back(it); + + std::unique_lock lck(mutex); + *it = pktQueue.begin(); + + if ( wait ) { + while ( ((! pktQueue.size()) or (*it == pktQueue.end())) and !zm_terminate and !deleting ) { + Debug(2, "waiting for packets in queue. Queue size %zu it == end? %d", pktQueue.size(), (*it == pktQueue.end())); + condition.wait(lck); + *it = pktQueue.begin(); + } + if ( deleting or zm_terminate ) { + free_it(it); + delete it; + return nullptr; + } + } + + while (*it != pktQueue.end()) { + 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 )) { + Debug(1, "Found a keyframe for stream %d, so returning the it to it", video_stream_id); + return it; + } + ++(*it); + } + Debug(1, "DIdn't Found a keyframe for stream %d, so returning the it to it", video_stream_id); + return it; +} // get video_it + +void PacketQueue::free_it(packetqueue_iterator *it) { + for ( + std::list::iterator iterators_it = iterators.begin(); + iterators_it != iterators.end(); + ++iterators_it + ) { + if ( *iterators_it == it ) { + iterators.erase(iterators_it); + break; + } + } +} + +bool PacketQueue::is_there_an_iterator_pointing_to_packet(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()) { + continue; + } + Debug(4, "Checking iterator %p == packet ? %d", std::addressof(*iterator_it), ( *(*iterator_it) == zm_packet )); + // Have to check each iterator and make sure it doesn't point to the packet we are about to delete + if (*(*iterator_it) == zm_packet) { + return true; + } + } // end foreach iterator + return false; +} + +void PacketQueue::setMaxVideoPackets(int p) { + max_video_packet_count = p; + Debug(1, "Setting max_video_packet_count to %d", p); + if (max_video_packet_count < 0) + max_video_packet_count = 0 ; +} +void PacketQueue::setPreEventVideoPackets(int p) { + pre_event_video_packet_count = p; + Debug(1, "Setting pre_event_video_packet_count to %d", p); + if (pre_event_video_packet_count < 1) + pre_event_video_packet_count = 1; + // We can simplify a lot of logic in queuePacket if we can assume at least 1 packet in queue +} + +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 31fe321cb..20cfdfe85 100644 --- a/src/zm_packetqueue.h +++ b/src/zm_packetqueue.h @@ -16,41 +16,73 @@ //You should have received a copy of the GNU General Public License //along with ZoneMinder. If not, see . - #ifndef ZM_PACKETQUEUE_H #define ZM_PACKETQUEUE_H -//#include -//#include -//#include +#include #include -#include "zm_packet.h" +#include +#include -extern "C" { -#include -} -class zm_packetqueue { -public: - zm_packetqueue(int max_stream_id); - virtual ~zm_packetqueue(); - bool queuePacket(AVPacket* packet, struct timeval *timestamp); - bool queuePacket(ZMPacket* packet); - bool queuePacket(AVPacket* packet); - ZMPacket * popPacket(); - bool popVideoPacket(ZMPacket* packet); - bool popAudioPacket(ZMPacket* packet); - unsigned int clearQueue(unsigned int video_frames_to_keep, int stream_id); - unsigned int clearQueue(struct timeval *duration, int streamid); - void clearQueue(); - void dumpQueue(); - unsigned int size(); - void clear_unwanted_packets(timeval *recording, int pre_event_count, int mVideoStreamId); - int packet_count(int stream_id); -private: - std::list pktQueue; +class ZMPacket; +class ZMLockedPacket; + +typedef std::list>::iterator packetqueue_iterator; + +class PacketQueue { + public: // For now just to ease development + std::list> pktQueue; + std::list>::iterator analysis_it; + + int video_stream_id; + int max_video_packet_count; // allow a negative value to someday mean unlimited + // This is now a hard limit on the # of video packets to keep in the queue so that we can limit ram + int pre_event_video_packet_count; // Was max_video_packet_count int max_stream_id; int *packet_counts; /* packet count for each stream_id, to keep track of how many video vs audio packets are in the queue */ + bool deleting; + bool keep_keyframes; + std::list iterators; + std::mutex mutex; + std::condition_variable condition; + + public: + PacketQueue(); + virtual ~PacketQueue(); + std::list>::const_iterator end() const { return pktQueue.end(); } + std::list>::const_iterator begin() const { return pktQueue.begin(); } + + int addStream(); + void setMaxVideoPackets(int p); + void setPreEventVideoPackets(int p); + void setKeepKeyframes(bool k) { keep_keyframes = k; }; + + bool queuePacket(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 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); + 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 *); + + packetqueue_iterator *get_event_start_packet_it( + packetqueue_iterator snapshot_it, + unsigned int pre_event_count + ); + 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_poll_thread.cpp b/src/zm_poll_thread.cpp new file mode 100644 index 000000000..ee46dbfe5 --- /dev/null +++ b/src/zm_poll_thread.cpp @@ -0,0 +1,32 @@ +#include "zm_poll_thread.h" + +#include "zm_monitor.h" +#include "zm_signal.h" +#include "zm_time.h" + +PollThread::PollThread(Monitor *monitor) : + monitor_(monitor), terminate_(false) { + thread_ = std::thread(&PollThread::Run, this); +} + +PollThread::~PollThread() { + Stop(); +} + +void PollThread::Start() { + if (thread_.joinable()) thread_.join(); + terminate_ = false; + Debug(3, "Starting polling thread"); + thread_ = std::thread(&PollThread::Run, this); +} +void PollThread::Stop() { + terminate_ = true; + if (thread_.joinable()) { + thread_.join(); + } +} +void PollThread::Run() { + while (!(terminate_ or zm_terminate)) { + monitor_->Poll(); + } +} diff --git a/src/zm_poll_thread.h b/src/zm_poll_thread.h new file mode 100644 index 000000000..16444a951 --- /dev/null +++ b/src/zm_poll_thread.h @@ -0,0 +1,29 @@ +#ifndef ZM_POLL_THREAD_H +#define ZM_POLL_THREAD_H + +#include +#include +#include + +class Monitor; + +class PollThread { + public: + explicit PollThread(Monitor *monitor); + ~PollThread(); + PollThread(PollThread &rhs) = delete; + PollThread(PollThread &&rhs) = delete; + + void Start(); + void Stop(); + bool Stopped() const { return terminate_; } + + private: + void Run(); + + Monitor *monitor_; + std::atomic terminate_; + std::thread thread_; +}; + +#endif diff --git a/src/zm_poly.cpp b/src/zm_poly.cpp index 13a21e7e0..6485bc192 100644 --- a/src/zm_poly.cpp +++ b/src/zm_poly.cpp @@ -17,106 +17,105 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" #include "zm_poly.h" -#ifndef SOLARIS -#include -#else +#include "zm_line.h" #include -#endif -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 ); + 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 0fcf34a6e..c25033262 100644 --- a/src/zm_poly.h +++ b/src/zm_poly.h @@ -20,97 +20,37 @@ #ifndef ZM_POLY_H #define ZM_POLY_H -#include "zm.h" -#include "zm_coord.h" #include "zm_box.h" +#include -#include - -// -// 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 = 0; - } - ~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( 0 ), area( 0 ), edges(0), slices(0) { - } - 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 HiX() const { return( extent.HiX() ); } - inline int LoY() const { return( extent.LoY() ); } - inline int HiY() const { return( extent.HiY() ); } - 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.cpp b/src/zm_regexp.cpp index 90c000028..1541b31ef 100644 --- a/src/zm_regexp.cpp +++ b/src/zm_regexp.cpp @@ -15,16 +15,16 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ +*/ -#include - -#include "zm.h" #include "zm_regexp.h" +#include "zm_logger.h" +#include + #if HAVE_LIBPCRE -RegExpr::RegExpr( const char *pattern, int flags, int p_max_matches ) : max_matches( p_max_matches ), match_buffers( 0 ), match_lengths( 0 ), match_valid( 0 ) +RegExpr::RegExpr( const char *pattern, int flags, int p_max_matches ) : max_matches( p_max_matches ), match_buffers( nullptr ), match_lengths( nullptr ), match_valid( nullptr ) { const char *errstr; int erroffset = 0; @@ -50,7 +50,7 @@ RegExpr::RegExpr( const char *pattern, int flags, int p_max_matches ) : max_matc match_valid = new bool[max_matches]; memset( match_valid, 0, sizeof(*match_valid)*max_matches ); } else { - match_vectors = NULL; + match_vectors = nullptr; } match_string = ""; n_matches = 0; diff --git a/src/zm_regexp.h b/src/zm_regexp.h index c859823d5..9c9ec5675 100644 --- a/src/zm_regexp.h +++ b/src/zm_regexp.h @@ -17,11 +17,11 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "zm.h" - #ifndef ZM_REGEXP_H #define ZM_REGEXP_H +#include "zm_config.h" + #if HAVE_LIBPCRE #if HAVE_PCRE_H @@ -51,7 +51,7 @@ protected: bool ok; public: - RegExpr( const char *pattern, int cflags=0, int p_max_matches=32 ); + explicit RegExpr( const char *pattern, int cflags=0, int p_max_matches=32 ); ~RegExpr(); bool Ok() const { return( ok ); } int MatchCount() const { return( n_matches ); } diff --git a/src/zm_remote_camera.cpp b/src/zm_remote_camera.cpp index d80532d54..3f45578ec 100644 --- a/src/zm_remote_camera.cpp +++ b/src/zm_remote_camera.cpp @@ -20,9 +20,12 @@ #include "zm_remote_camera.h" #include "zm_utils.h" +#include +#include +#include RemoteCamera::RemoteCamera( - unsigned int p_monitor_id, + const Monitor *monitor, const std::string &p_protocol, const std::string &p_host, const std::string &p_port, @@ -37,54 +40,52 @@ RemoteCamera::RemoteCamera( bool p_capture, bool p_record_audio ) : - Camera( p_monitor_id, REMOTE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), + Camera( monitor, REMOTE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), protocol( p_protocol ), host( p_host ), port( p_port ), path( p_path ), hp( 0 ), mNeedAuth(false), - mAuthenticator(NULL) + mAuthenticator(nullptr) { - if ( path[0] != '/' ) + if (path[0] != '/') path = '/'+path; } RemoteCamera::~RemoteCamera() { - if ( hp != NULL ) { + if (hp != nullptr) { freeaddrinfo(hp); - hp = NULL; + hp = nullptr; } - if ( mAuthenticator ) { + if (mAuthenticator) { delete mAuthenticator; - mAuthenticator = NULL; + 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; @@ -96,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 = NULL; + struct addrinfo *p = nullptr; int addr_count = 0; - for ( p = hp; p != NULL; 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 63396df95..e08975af2 100644 --- a/src/zm_remote_camera.h +++ b/src/zm_remote_camera.h @@ -22,12 +22,7 @@ #include "zm_camera.h" #include "zm_rtsp_auth.h" - #include -#include -#include -#include -#include #define SOCKET_BUF_SIZE 8192 @@ -57,7 +52,7 @@ protected: public: RemoteCamera( - unsigned int p_monitor_id, + const Monitor *monitor, const std::string &p_proto, const std::string &p_host, const std::string &p_port, @@ -74,24 +69,23 @@ public: ); virtual ~RemoteCamera(); - const std::string &Protocol() const { return( protocol ); } - const std::string &Host() const { return( host ); } - const std::string &Port() const { return( port ); } - const std::string &Path() const { return( path ); } - const std::string &Auth() const { return( auth ); } - const std::string &Username() const { return( username ); } - const std::string &Password() const { return( password ); } + const std::string &Protocol() const { return protocol; } + const std::string &Host() const { return host; } + const std::string &Port() const { return port; } + const std::string &Path() const { return path; } + const std::string &Auth() const { return auth; } + const std::string &Username() const { return username; } + const std::string &Password() const { return password; } virtual void Initialise(); virtual void Terminate() = 0; virtual int Connect() = 0; virtual int Disconnect() = 0; - virtual int PreCapture() { return 0; }; - virtual int PrimeCapture() { return 0; }; - virtual int Capture( Image &image ) = 0; - virtual int PostCapture() = 0; - virtual int CaptureAndRecord( Image &image, timeval recording, char* event_directory )=0; - int Read( int fd, char*buf, int size ); + virtual int PreCapture() override { return 0; }; + virtual int PrimeCapture() override { return 0; }; + virtual int Capture(std::shared_ptr &p) override = 0; + virtual int PostCapture() override = 0; + int Read(int fd, char*buf, int size); }; #endif // ZM_REMOTE_CAMERA_H diff --git a/src/zm_remote_camera_http.cpp b/src/zm_remote_camera_http.cpp index 96c3707ff..6169149da 100644 --- a/src/zm_remote_camera_http.cpp +++ b/src/zm_remote_camera_http.cpp @@ -18,15 +18,16 @@ // #include "zm_remote_camera_http.h" -#include "zm_rtsp_auth.h" -#include "zm_mem_utils.h" +#include "zm_monitor.h" +#include "zm_packet.h" #include "zm_signal.h" - -#include -#include -#include +#include "zm_regexp.h" +#include "zm_utils.h" +#include #include +#include +#include #ifdef SOLARIS #include // FIONREAD and friends @@ -36,15 +37,15 @@ #endif #if HAVE_LIBPCRE -static RegExpr *header_expr = 0; -static RegExpr *status_expr = 0; -static RegExpr *connection_expr = 0; -static RegExpr *content_length_expr = 0; -static RegExpr *content_type_expr = 0; +static RegExpr *header_expr = nullptr; +static RegExpr *status_expr = nullptr; +static RegExpr *connection_expr = nullptr; +static RegExpr *content_length_expr = nullptr; +static RegExpr *content_type_expr = nullptr; #endif RemoteCameraHttp::RemoteCameraHttp( - unsigned int p_monitor_id, + const Monitor *monitor, const std::string &p_method, const std::string &p_host, const std::string &p_port, @@ -58,7 +59,7 @@ RemoteCameraHttp::RemoteCameraHttp( bool p_capture, bool p_record_audio ) : RemoteCamera( - p_monitor_id, + monitor, "http", p_host, p_port, @@ -83,10 +84,11 @@ RemoteCameraHttp::RemoteCameraHttp( else if ( p_method == "regexp" ) { method = REGEXP; } else - Fatal( "Unrecognised method '%s' when creating HTTP camera %d", p_method.c_str(), monitor_id ); + Fatal("Unrecognised method '%s' when creating HTTP camera %d", p_method.c_str(), monitor->Id()); if ( capture ) { Initialise(); } + mVideoStream = nullptr; } RemoteCameraHttp::~RemoteCameraHttp() { @@ -103,7 +105,7 @@ void RemoteCameraHttp::Initialise() { request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); request += stringtf( "Host: %s\r\n", host.c_str()); if ( strcmp( config.http_version, "1.0" ) == 0 ) - request += stringtf( "Connection: Keep-Alive\r\n" ); + request += "Connection: Keep-Alive\r\n"; if ( !auth.empty() ) request += stringtf( "Authorization: Basic %s\r\n", auth64.c_str() ); request += "\r\n"; @@ -140,9 +142,17 @@ void RemoteCameraHttp::Initialise() { } // end void RemoteCameraHttp::Initialise() int RemoteCameraHttp::Connect() { - struct addrinfo *p = NULL; + struct addrinfo *p = nullptr; - for ( p = hp; p != NULL; p = p->ai_next ) { + 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 ) { Warning("Can't create socket: %s", strerror(errno) ); @@ -157,7 +167,7 @@ int RemoteCameraHttp::Connect() { addr = (struct sockaddr_in *)p->ai_addr; inet_ntop( AF_INET, &(addr->sin_addr), buf, INET6_ADDRSTRLEN ); - Warning("Can't connect to remote camera mid: %d at %s: %s", monitor_id, buf, strerror(errno) ); + Warning("Can't connect to remote camera mid: %d at %s: %s", monitor->Id(), buf, strerror(errno)); continue; } @@ -165,7 +175,7 @@ int RemoteCameraHttp::Connect() { break; } - if ( p == NULL ) { + if ( p == nullptr ) { Error("Unable to connect to the remote camera, aborting"); return -1; } @@ -200,112 +210,130 @@ 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); struct timeval temp_timeout = timeout; - int n_found = select(sd+1, &rfds, NULL, NULL, &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 ); + int n_found = select(sd+1, &rfds, nullptr, nullptr, &temp_timeout); + 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) ); - return( -1 ); + 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) { 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. - return( 0 ); + return 0; } // If socket is closed locally, then select will fail, but if it is closed remotely // then we have an exception on our socket.. but no data. - Debug( 3, "Socket closed remotely" ); + Debug(3, "Socket closed remotely"); //Disconnect(); // Disconnect is done outside of ReadData now. - return( -1 ); + return -1; } // There can be lots of bytes available. I've seen 4MB or more. This will vastly inflate our buffer size unnecessarily. - if ( total_bytes_to_read > ZM_NETWORK_BUFSIZ ) { + if (total_bytes_to_read > ZM_NETWORK_BUFSIZ) { total_bytes_to_read = ZM_NETWORK_BUFSIZ; - Debug(3, "Just getting 32K" ); + Debug(4, "Just getting 32K"); } else { - Debug(3, "Just getting %d", total_bytes_to_read ); + Debug(4, "Just getting %d", total_bytes_to_read); } } // end if bytes_expected or not - Debug( 3, "Expecting %d bytes", total_bytes_to_read ); + Debug(4, "Expecting %d bytes", total_bytes_to_read); int total_bytes_read = 0; do { - int bytes_read = buffer.read_into( sd, total_bytes_to_read ); - if ( bytes_read < 0 ) { - Error( "Read error: %s", strerror(errno) ); - return( -1 ); - } else if ( bytes_read == 0 ) { - Debug( 2, "Socket closed" ); + int bytes_read = buffer.read_into(sd, total_bytes_to_read); + if (bytes_read < 0) { + Error("Read error: %s", strerror(errno)); + return -1; + } else if (bytes_read == 0) { + Debug(2, "Socket closed"); //Disconnect(); // Disconnect is done outside of ReadData now. - return( -1 ); - } else if ( (unsigned int)bytes_read < total_bytes_to_read ) { - Error( "Incomplete read, expected %d, got %d", total_bytes_to_read, bytes_read ); - return( -1 ); + return -1; + } else if ((unsigned int)bytes_read < total_bytes_to_read) { + Debug(1, "Incomplete read, expected %d, got %d", total_bytes_to_read, bytes_read); + } else { + Debug(3, "Read %d bytes", bytes_read); } - Debug( 3, "Read %d bytes", bytes_read ); total_bytes_read += bytes_read; total_bytes_to_read -= bytes_read; - } while ( total_bytes_to_read ); + } while (total_bytes_to_read); - Debug(4, buffer); + Debug(4, "buffer size: %d", static_cast(buffer)); return total_bytes_read; +} // end readData + +int RemoteCameraHttp::GetData() { + 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 = 0; + const char *header = nullptr; int header_len = 0; - const char *http_version = 0; + const char *http_version = nullptr; int status_code = 0; - const char *status_mesg = 0; + const char *status_mesg = nullptr; const char *connection_type = ""; int content_length = 0; const char *content_type = ""; const char *content_boundary = ""; - const char *subheader = 0; + const char *subheader = nullptr; int subheader_len = 0; //int subcontent_length = 0; //const char *subcontent_type = ""; @@ -314,9 +342,7 @@ int RemoteCameraHttp::GetResponse() { switch( state ) { case HEADER : { - while ( !( buffer_len = ReadData(buffer) ) && !zm_terminate ) { - Debug(4, "Timeout waiting for REGEXP HEADER"); - } + int buffer_len = GetData(); if ( buffer_len < 0 ) { Error("Unable to read header data"); return -1; @@ -327,8 +353,7 @@ int RemoteCameraHttp::GetResponse() { header_len = header_expr->MatchLength( 1 ); Debug(4, "Captured header (%d bytes):\n'%s'", header_len, header); - if ( status_expr->Match( header, header_len ) < 4 ) - { + if ( status_expr->Match( header, header_len ) < 4 ) { Error( "Unable to extract HTTP status from header" ); return( -1 ); } @@ -351,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"; @@ -365,457 +390,25 @@ int RemoteCameraHttp::GetResponse() { } Debug( 3, "Got status '%d' (%s), http version %s", status_code, status_mesg, http_version ); - if ( connection_expr->Match( header, header_len ) == 2 ) - { + if ( connection_expr->Match( header, header_len ) == 2 ) { connection_type = connection_expr->MatchString( 1 ); Debug( 3, "Got connection '%s'", connection_type ); } - if ( content_length_expr->Match( header, header_len ) == 2 ) - { + if ( content_length_expr->Match( header, header_len ) == 2 ) { content_length = atoi( content_length_expr->MatchString( 1 ) ); Debug( 3, "Got content length '%d'", content_length ); } - if ( content_type_expr->Match( header, header_len ) >= 2 ) - { + if ( content_type_expr->Match( header, header_len ) >= 2 ) { content_type = content_type_expr->MatchString( 1 ); Debug( 3, "Got content type '%s'\n", content_type ); - if ( content_type_expr->MatchCount() > 2 ) - { + if ( content_type_expr->MatchCount() > 2 ) { content_boundary = content_type_expr->MatchString( 2 ); Debug( 3, "Got content boundary '%s'", content_boundary ); } } - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = JPEG; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "image/x-rgb" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = X_RGB; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = X_RGBZ; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) - { - // Image stream, so start processing - if ( !content_boundary[0] ) - { - Error( "No content boundary found in header '%s'", header ); - return( -1 ); - } - mode = MULTI_IMAGE; - state = SUBHEADER; - } - //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) - //{ - //// MPEG stream, coming soon! - //} - else - { - Error( "Unrecognised content type '%s'", content_type ); - return( -1 ); - } - buffer.consume( header_len ); - } - else - { - Debug( 3, "Unable to extract header from stream, retrying" ); - //return( -1 ); - } - break; - } - case SUBHEADER : - { - static RegExpr *subheader_expr = 0; - static RegExpr *subcontent_length_expr = 0; - static RegExpr *subcontent_type_expr = 0; - - if ( !subheader_expr ) - { - char subheader_pattern[256] = ""; - snprintf( subheader_pattern, sizeof(subheader_pattern), "^((?:\r?\n){0,2}?(?:--)?%s\r?\n.+?\r?\n\r?\n)", content_boundary ); - subheader_expr = new RegExpr( subheader_pattern, PCRE_DOTALL ); - } - if ( subheader_expr->Match( (char *)buffer, (int)buffer ) == 2 ) - { - subheader = subheader_expr->MatchString( 1 ); - subheader_len = subheader_expr->MatchLength( 1 ); - Debug( 4, "Captured subheader (%d bytes):'%s'", subheader_len, subheader ); - - if ( !subcontent_length_expr ) - subcontent_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); - if ( subcontent_length_expr->Match( subheader, subheader_len ) == 2 ) - { - content_length = atoi( subcontent_length_expr->MatchString( 1 ) ); - Debug( 3, "Got subcontent length '%d'", content_length ); - } - - if ( !subcontent_type_expr ) - subcontent_type_expr = new RegExpr( "Content-type: ?(.+?)\r?\n", PCRE_CASELESS ); - if ( subcontent_type_expr->Match( subheader, subheader_len ) == 2 ) - { - content_type = subcontent_type_expr->MatchString( 1 ); - Debug( 3, "Got subcontent type '%s'", content_type ); - } - - buffer.consume( subheader_len ); - state = CONTENT; - } - else - { - Debug( 3, "Unable to extract subheader from stream, retrying" ); - while ( !( buffer_len = ReadData(buffer) ) && !zm_terminate ) { - Debug(4, "Timeout waiting to extract subheader"); - } - if ( buffer_len < 0 ) { - Error( "Unable to extract subheader data" ); - return( -1 ); - } - bytes += buffer_len; - } - break; - } - case CONTENT : - { - - // if content_type is something like image/jpeg;size=, this will strip the ;size= - char * semicolon = strchr( (char *)content_type, ';' ); - if ( semicolon ) { - *semicolon = '\0'; - } - - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) - { - format = JPEG; - } - else if ( !strcasecmp( content_type, "image/x-rgb" ) ) - { - format = X_RGB; - } - else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) - { - format = X_RGBZ; - } - else - { - Error( "Found unsupported content type '%s'", content_type ); - return( -1 ); - } - - if ( content_length ) - { - while ( ((long)buffer.size() < content_length ) && ! zm_terminate ) - { - Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_length ); - int bytes_read = ReadData( buffer ); - - if ( bytes_read < 0 ) { - Error( "Unable to read content" ); - return( -1 ); - } - bytes += bytes_read; - } - Debug( 3, "Got end of image by length, content-length = %d", content_length ); - } - else - { - while ( !content_length ) - { - while ( !( buffer_len = ReadData(buffer) ) && !zm_terminate ) { - Debug(4, "Timeout waiting for content"); - } - if ( buffer_len < 0 ) { - Error( "Unable to read content" ); - return( -1 ); - } - bytes += buffer_len; - static RegExpr *content_expr = 0; - if ( mode == MULTI_IMAGE ) - { - if ( !content_expr ) - { - char content_pattern[256] = ""; - snprintf( content_pattern, sizeof(content_pattern), "^(.+?)(?:\r?\n)*(?:--)?%s\r?\n", content_boundary ); - content_expr = new RegExpr( content_pattern, PCRE_DOTALL ); - } - if ( content_expr->Match( buffer, buffer.size() ) == 2 ) - { - content_length = content_expr->MatchLength( 1 ); - Debug( 3, "Got end of image by pattern, content-length = %d", content_length ); - } - } - } - } - if ( mode == SINGLE_IMAGE ) { - state = HEADER; - Disconnect(); - } else { - state = SUBHEADER; - } - Debug( 3, "Returning %d (%d) bytes of captured content", content_length, buffer.size() ); - return content_length; - } - case HEADERCONT : - case SUBHEADERCONT : - { - // Ignore - break; - } - } - } - } else -#endif // HAVE_LIBPCRE - { - static const char *http_match = "HTTP/"; - static const char *connection_match = "Connection:"; - static const char *content_length_match = "Content-length:"; - static const char *content_type_match = "Content-type:"; - static const char *boundary_match = "boundary="; - static const char *authenticate_match = "WWW-Authenticate:"; - static int http_match_len = 0; - static int connection_match_len = 0; - static int content_length_match_len = 0; - static int content_type_match_len = 0; - static int boundary_match_len = 0; - static int authenticate_match_len = 0; - - if ( !http_match_len ) - http_match_len = strlen( http_match ); - if ( !connection_match_len ) - connection_match_len = strlen( connection_match ); - if ( !content_length_match_len ) - content_length_match_len = strlen( content_length_match ); - if ( !content_type_match_len ) - content_type_match_len = strlen( content_type_match ); - if ( !boundary_match_len ) - boundary_match_len = strlen( boundary_match ); - if ( !authenticate_match_len ) - authenticate_match_len = strlen( authenticate_match ); - - static int n_headers; - //static char *headers[32]; - - static int n_subheaders; - //static char *subheaders[32]; - - static char *http_header; - static char *connection_header; - static char *content_length_header; - static char *content_type_header; - static char *boundary_header; - static char *authenticate_header; - static char subcontent_length_header[32]; - static char subcontent_type_header[64]; - - static char http_version[16]; - static char status_code[16]; - static char status_mesg[256]; - static char connection_type[32]; - static int content_length; - static char content_type[32]; - static char content_boundary[64]; - static int content_boundary_len; - - while ( !zm_terminate ) { - switch( state ) { - case HEADER : - { - n_headers = 0; - http_header = 0; - connection_header = 0; - content_length_header = 0; - content_type_header = 0; - authenticate_header = 0; - - http_version[0] = '\0'; - status_code [0]= '\0'; - status_mesg [0]= '\0'; - connection_type [0]= '\0'; - content_length = 0; - content_type[0] = '\0'; - content_boundary[0] = '\0'; - content_boundary_len = 0; - } - case HEADERCONT : - { - while ( !( buffer_len = ReadData(buffer) ) && !zm_terminate ) { - Debug(1, "Timeout waiting for HEADERCONT"); - } - if ( buffer_len < 0 ) { - Error("Unable to read header"); - return -1; - } - bytes += buffer_len; - - char *crlf = 0; - char *header_ptr = (char *)buffer; - int header_len = buffer.size(); - bool all_headers = false; - - while( true ) { - int crlf_len = memspn(header_ptr, "\r\n", header_len); - if ( n_headers ) { - if ( (crlf_len == 2 && !strncmp( header_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( header_ptr, "\r\n\r\n", crlf_len )) ) { - Debug(3, "Have double linefeed, done headers"); - *header_ptr = '\0'; - header_ptr += crlf_len; - header_len -= buffer.consume( header_ptr-(char *)buffer ); - all_headers = true; - break; - } - } - if ( crlf_len ) { - if ( header_len == crlf_len ) { - break; - } else { - *header_ptr = '\0'; - header_ptr += crlf_len; - header_len -= buffer.consume( header_ptr-(char *)buffer ); - } - } - - Debug( 6, "%s", header_ptr ); - if ( (crlf = mempbrk( header_ptr, "\r\n", header_len )) ) { - //headers[n_headers++] = header_ptr; - n_headers++; - - if ( !http_header && (strncasecmp( header_ptr, http_match, http_match_len ) == 0) ) { - http_header = header_ptr+http_match_len; - Debug( 6, "Got http header '%s'", header_ptr ); - } else if ( !connection_header && (strncasecmp( header_ptr, connection_match, connection_match_len) == 0) ) { - connection_header = header_ptr+connection_match_len; - Debug( 6, "Got connection header '%s'", header_ptr ); - } else if ( !content_length_header && (strncasecmp( header_ptr, content_length_match, content_length_match_len) == 0) ) { - content_length_header = header_ptr+content_length_match_len; - Debug( 6, "Got content length header '%s'", header_ptr ); - } else if ( !authenticate_header && (strncasecmp( header_ptr, authenticate_match, authenticate_match_len) == 0) ) { - authenticate_header = header_ptr; - Debug( 6, "Got authenticate header '%s'", header_ptr ); - } else if ( !content_type_header && (strncasecmp( header_ptr, content_type_match, content_type_match_len) == 0) ) { - content_type_header = header_ptr+content_type_match_len; - Debug( 6, "Got content type header '%s'", header_ptr ); - } else { - Debug( 6, "Got ignored header '%s'", header_ptr ); - } - header_ptr = crlf; - header_len -= buffer.consume( header_ptr-(char *)buffer ); - } else { - // No end of line found - break; - } - } // end while search for headers - - if ( all_headers ) { - char *start_ptr, *end_ptr; - - if ( !http_header ) { - Error( "Unable to extract HTTP status from header" ); - return( -1 ); - } - - start_ptr = http_header; - end_ptr = start_ptr+strspn( start_ptr, "10." ); - - // FIXME Why are we memsetting every time? Can we not do it once? - memset( http_version, 0, sizeof(http_version) ); - strncpy( http_version, start_ptr, end_ptr-start_ptr ); - - start_ptr = end_ptr; - start_ptr += strspn( start_ptr, " " ); - end_ptr = start_ptr+strspn( start_ptr, "0123456789" ); - - memset( status_code, 0, sizeof(status_code) ); - strncpy( status_code, start_ptr, end_ptr-start_ptr ); - int status = atoi( status_code ); - - start_ptr = end_ptr; - start_ptr += strspn( start_ptr, " " ); - strcpy( status_mesg, start_ptr ); - - if ( status == 401 ) { - if ( mNeedAuth ) { - Error( "Failed authentication: " ); - return( -1 ); - } - if ( ! authenticate_header ) { - Error( "Failed authentication, but don't have an authentication header: " ); - return( -1 ); - } - mNeedAuth = true; - std::string Header = authenticate_header; - Debug(2, "Checking for digest auth in %s", authenticate_header ); - - mAuthenticator->checkAuthResponse(Header); - if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { - Debug( 2, "Need Digest Authentication" ); - request = stringtf( "GET %s HTTP/%s\r\n", path.c_str(), config.http_version ); - request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); - request += stringtf( "Host: %s\r\n", host.c_str()); - if ( strcmp( config.http_version, "1.0" ) == 0 ) - request += stringtf( "Connection: Keep-Alive\r\n" ); - request += mAuthenticator->getAuthHeader( "GET", path.c_str() ); - request += "\r\n"; - - Debug( 2, "New request header: %s", request.c_str() ); - return( 0 ); - } else { - Debug( 2, "Need some other kind of Authentication" ); - } - } else if ( status < 200 || status > 299 ) { - Error( "Invalid response status %s: %s", status_code, status_mesg ); - return( -1 ); - } - Debug( 3, "Got status '%d' (%s), http version %s", status, status_mesg, http_version ); - - if ( connection_header ) { - memset( connection_type, 0, sizeof(connection_type) ); - start_ptr = connection_header + strspn( connection_header, " " ); - // FIXME Should we not use strncpy? - strcpy( connection_type, start_ptr ); - Debug( 3, "Got connection '%s'", connection_type ); - } - if ( content_length_header ) { - start_ptr = content_length_header + strspn( content_length_header, " " ); - content_length = atoi( start_ptr ); - Debug( 3, "Got content length '%d'", content_length ); - } - if ( content_type_header ) { - memset( content_type, 0, sizeof(content_type) ); - start_ptr = content_type_header + strspn( content_type_header, " " ); - if ( (end_ptr = strchr( start_ptr, ';' )) ) { - strncpy( content_type, start_ptr, end_ptr-start_ptr ); - Debug( 3, "Got content type '%s'", content_type ); - - start_ptr = end_ptr + strspn( end_ptr, "; " ); - - if ( strncasecmp( start_ptr, boundary_match, boundary_match_len ) == 0 ) { - start_ptr += boundary_match_len; - start_ptr += strspn( start_ptr, "-" ); - content_boundary_len = sprintf( content_boundary, "--%s", start_ptr ); - Debug( 3, "Got content boundary '%s'", content_boundary ); - } else { - Error( "No content boundary found in header '%s'", content_type_header ); - } - } else { - strcpy( content_type, start_ptr ); - Debug( 3, "Got content type '%s'", content_type ); - } - } // end if content_type_header - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { // Single image mode = SINGLE_IMAGE; @@ -834,7 +427,7 @@ int RemoteCameraHttp::GetResponse() { } else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) { // Image stream, so start processing if ( !content_boundary[0] ) { - Error( "No content boundary found in header '%s'", content_type_header ); + Error( "No content boundary found in header '%s'", header ); return( -1 ); } mode = MULTI_IMAGE; @@ -845,116 +438,64 @@ 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); } else { - Debug(3, "Unable to extract entire header from stream, continuing"); - state = HEADERCONT; + Debug(3, "Unable to extract header from stream, retrying"); //return( -1 ); - } // end if all_headers + } 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'; - } - case SUBHEADERCONT : - { - char *crlf = 0; - char *subheader_ptr = (char *)buffer; - int subheader_len = buffer.size(); - bool all_headers = false; + static RegExpr *subheader_expr = nullptr; + static RegExpr *subcontent_length_expr = nullptr; + static RegExpr *subcontent_type_expr = nullptr; - 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 )) ) { - *subheader_ptr = '\0'; - subheader_ptr += crlf_len; - subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); - all_headers = true; - break; - } - } - 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 ); - } - } - - Debug( 6, "%d: %s", subheader_len, subheader_ptr ); - - if ( (crlf = mempbrk( subheader_ptr, "\r\n", subheader_len )) ) { - //subheaders[n_subheaders++] = subheader_ptr; - n_subheaders++; - - if ( !boundary_header && (strncasecmp( subheader_ptr, content_boundary, content_boundary_len ) == 0) ) { - boundary_header = subheader_ptr; - Debug( 4, "Got boundary subheader '%s'", subheader_ptr ); - } else if ( !subcontent_length_header[0] && (strncasecmp( subheader_ptr, content_length_match, content_length_match_len) == 0) ) { - strncpy( subcontent_length_header, subheader_ptr+content_length_match_len, sizeof(subcontent_length_header) ); - *(subcontent_length_header+strcspn( subcontent_length_header, "\r\n" )) = '\0'; - Debug( 4, "Got content length subheader '%s'", subcontent_length_header ); - } else if ( !subcontent_type_header[0] && (strncasecmp( subheader_ptr, content_type_match, content_type_match_len) == 0) ) { - strncpy( subcontent_type_header, subheader_ptr+content_type_match_len, sizeof(subcontent_type_header) ); - *(subcontent_type_header+strcspn( subcontent_type_header, "\r\n" )) = '\0'; - Debug( 4, "Got content type subheader '%s'", subcontent_type_header ); - } else { - Debug( 6, "Got ignored subheader '%s' found", subheader_ptr ); - } - subheader_ptr = crlf; - subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); - } else { - // No line end found - break; - } + if ( !subheader_expr ) { + char subheader_pattern[256] = ""; + snprintf( subheader_pattern, sizeof(subheader_pattern), "^((?:\r?\n){0,2}?(?:--)?%s\r?\n.+?\r?\n\r?\n)", content_boundary ); + subheader_expr = new RegExpr( subheader_pattern, PCRE_DOTALL ); } + if ( subheader_expr->Match( (char *)buffer, (int)buffer ) == 2 ) { + subheader = subheader_expr->MatchString( 1 ); + subheader_len = subheader_expr->MatchLength( 1 ); + Debug( 4, "Captured subheader (%d bytes):'%s'", subheader_len, subheader ); - if ( all_headers && boundary_header ) { - char *start_ptr/*, *end_ptr*/; - - 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 ); + if ( !subcontent_length_expr ) + subcontent_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); + if ( subcontent_length_expr->Match( subheader, subheader_len ) == 2 ) { + content_length = atoi( subcontent_length_expr->MatchString( 1 ) ); Debug( 3, "Got subcontent length '%d'", content_length ); } - if ( subcontent_type_header[0] ) { - memset( content_type, 0, sizeof(content_type) ); - start_ptr = subcontent_type_header + strspn( subcontent_type_header, " " ); - strcpy( content_type, start_ptr ); + + if ( !subcontent_type_expr ) + subcontent_type_expr = new RegExpr( "Content-type: ?(.+?)\r?\n", PCRE_CASELESS ); + if ( subcontent_type_expr->Match( subheader, subheader_len ) == 2 ) { + content_type = subcontent_type_expr->MatchString( 1 ); Debug( 3, "Got subcontent type '%s'", content_type ); } + + buffer.consume( subheader_len ); state = CONTENT; } else { Debug( 3, "Unable to extract subheader from stream, retrying" ); - while ( !( buffer_len = ReadData(buffer) ) &&!zm_terminate ) { - Debug(1, "Timeout waiting to extra subheader non regexp"); - } + int buffer_len = GetData(); if ( buffer_len < 0 ) { - Error( "Unable to read subheader" ); + Error( "Unable to extract subheader data" ); return( -1 ); } bytes += buffer_len; - state = SUBHEADERCONT; } break; } - case CONTENT : { + case CONTENT : + { // if content_type is something like image/jpeg;size=, this will strip the ;size= - char * semicolon = strchr( content_type, ';' ); + char * semicolon = strchr( (char *)content_type, ';' ); if ( semicolon ) { *semicolon = '\0'; } @@ -970,97 +511,560 @@ int RemoteCameraHttp::GetResponse() { 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 (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 ( content_length ) { - while ( ( (long)buffer.size() < content_length ) && ! zm_terminate ) { - Debug(4, "getting more data"); - int bytes_read = ReadData(buffer); - if ( bytes_read < 0 ) { + 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 ) { - Debug(4, "!content_length, ReadData"); - buffer_len = ReadData( buffer ); - 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; - int buffer_size = buffer.size(); - if ( buffer_len ) { - // Got some data - - 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 ) ) { - content_length = start_ptr - (char *)buffer; - 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 ); - } - } // 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 ) { - char *end_ptr = (char *)buffer+buffer_size; - - // strip off any last line feeds - 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 ); - } - } // end if SINGLE_IMAGE - } // end if read some data - } // end while ! content_length - } // end if content_length - + static RegExpr *content_expr = 0; + if (mode == MULTI_IMAGE) { + if (!content_expr) { + char content_pattern[256] = ""; + snprintf(content_pattern, sizeof(content_pattern), "^(.+?)(?:\r?\n)*(?:--)?%s\r?\n", content_boundary); + content_expr = new RegExpr(content_pattern, PCRE_DOTALL); + } + if (content_expr->Match( buffer, buffer.size()) == 2) { + content_length = content_expr->MatchLength( 1 ); + Debug(3, "Got end of image by pattern, content-length = %d", content_length); + } + } + } + } if ( mode == SINGLE_IMAGE ) { state = HEADER; Disconnect(); } else { state = SUBHEADER; } + Debug( 3, "Returning %d (%d) bytes of captured content", content_length, buffer.size() ); + return content_length; + } + case HEADERCONT : + case SUBHEADERCONT : + // Ignore + break; + } + } + } else +#endif // HAVE_LIBPCRE + { + static const char *http_match = "HTTP/"; + static const char *connection_match = "Connection:"; + static const char *content_length_match = "Content-length:"; + static const char *content_type_match = "Content-type:"; + static const char *boundary_match = "boundary="; + static const char *authenticate_match = "WWW-Authenticate:"; + static int http_match_len = 0; + static int connection_match_len = 0; + static int content_length_match_len = 0; + static int content_type_match_len = 0; + static int boundary_match_len = 0; + static int authenticate_match_len = 0; - if ( 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 ( !http_match_len ) + http_match_len = strlen(http_match); + if ( !connection_match_len ) + connection_match_len = strlen(connection_match); + if ( !content_length_match_len ) + content_length_match_len = strlen(content_length_match); + if ( !content_type_match_len ) + content_type_match_len = strlen(content_type_match); + if ( !boundary_match_len ) + boundary_match_len = strlen(boundary_match); + if ( !authenticate_match_len ) + authenticate_match_len = strlen(authenticate_match); + + static int n_headers; + static int n_subheaders; + + static char *http_header; + static char *connection_header; + static char *content_length_header; + static char *content_type_header; + static char *boundary_header; + static char *authenticate_header; + static char subcontent_length_header[33]; + static char subcontent_type_header[65]; + + static char http_version[16]; + static char status_code[16]; + static char status_mesg[256]; + static char connection_type[32]; + static int content_length; + static char content_type[32]; + static char content_boundary[64]; + static int content_boundary_len; + + while (!zm_terminate) { + switch (state) { + case HEADER : + n_headers = 0; + http_header = nullptr; + connection_header = nullptr; + content_length_header = nullptr; + content_type_header = nullptr; + authenticate_header = nullptr; + + http_version[0] = '\0'; + status_code [0]= '\0'; + status_mesg [0]= '\0'; + connection_type [0]= '\0'; + content_length = 0; + content_type[0] = '\0'; + content_boundary[0] = '\0'; + content_boundary_len = 0; + FALLTHROUGH; + case HEADERCONT : + { + int buffer_len = GetData(); + if (buffer_len < 0) { + Error("Unable to read header"); + return -1; + } + bytes += buffer_len; + + char *crlf = nullptr; + char *header_ptr = buffer; + int header_len = buffer.size(); + bool all_headers = false; + + while (!zm_terminate) { + int crlf_len = memspn(header_ptr, "\r\n", header_len); + if (n_headers) { + if ( + (crlf_len == 2 && !strncmp(header_ptr, "\n\n", crlf_len)) + || + (crlf_len == 4 && !strncmp(header_ptr, "\r\n\r\n", crlf_len)) + ) { + Debug(3, "Have double linefeed, done headers"); + *header_ptr = '\0'; + header_ptr += crlf_len; + header_len -= buffer.consume(header_ptr-(char *)buffer); + all_headers = true; + break; + } + } + if (crlf_len) { + if (header_len == crlf_len) { + break; + } else { + *header_ptr = '\0'; + header_ptr += crlf_len; + header_len -= buffer.consume(header_ptr-(char *)buffer); + } + } + + Debug(6, "%s", header_ptr); + if ((crlf = mempbrk(header_ptr, "\r\n", header_len))) { + //headers[n_headers++] = header_ptr; + n_headers++; + + if (!http_header && (strncasecmp(header_ptr, http_match, http_match_len) == 0)) { + http_header = header_ptr+http_match_len; + Debug(6, "Got http header '%s'", header_ptr); + } else if ( !connection_header && (strncasecmp(header_ptr, connection_match, connection_match_len) == 0) ) { + connection_header = header_ptr+connection_match_len; + Debug(6, "Got connection header '%s'", header_ptr); + } else if ( !content_length_header && (strncasecmp(header_ptr, content_length_match, content_length_match_len) == 0) ) { + content_length_header = header_ptr+content_length_match_len; + Debug(6, "Got content length header '%s'", header_ptr); + } else if ( !authenticate_header && (strncasecmp(header_ptr, authenticate_match, authenticate_match_len) == 0) ) { + authenticate_header = header_ptr; + Debug(6, "Got authenticate header '%s'", header_ptr); + } else if ( !content_type_header && (strncasecmp(header_ptr, content_type_match, content_type_match_len) == 0) ) { + content_type_header = header_ptr+content_type_match_len; + Debug(6, "Got content type header '%s'", header_ptr); + } else { + Debug(6, "Got ignored header '%s'", header_ptr); + } + header_ptr = crlf; + header_len -= buffer.consume(header_ptr-(char *)buffer); + } else { + // No end of line found + break; + } + } // end while search for headers + + if (all_headers) { + char *start_ptr, *end_ptr; + + if (!http_header) { + Error("Unable to extract HTTP status from header"); + return -1; + } + + start_ptr = http_header; + end_ptr = start_ptr+strspn(start_ptr, "10."); + + // FIXME Why are we memsetting every time? Can we not do it once? + //memset(http_version, 0, sizeof(http_version)); + strncpy(http_version, start_ptr, end_ptr-start_ptr); + + start_ptr = end_ptr; + start_ptr += strspn(start_ptr, " "); + end_ptr = start_ptr+strspn(start_ptr, "0123456789"); + + memset(status_code, 0, sizeof(status_code)); + strncpy(status_code, start_ptr, end_ptr-start_ptr); + int status = atoi(status_code); + + start_ptr = end_ptr; + start_ptr += strspn(start_ptr, " "); + strcpy(status_mesg, start_ptr); + + if (status == 401) { + if (mNeedAuth) { + Error("Failed authentication"); + return -1; + } + if (!authenticate_header) { + Error("Failed authentication, but don't have an authentication header."); + return -1; + } + mNeedAuth = true; + std::string Header = authenticate_header; + Debug(2, "Checking for digest auth in %s", authenticate_header); + + mAuthenticator->checkAuthResponse(Header); + if (mAuthenticator->auth_method() == zm::AUTH_DIGEST) { + Debug(2, "Need Digest Authentication"); + request = stringtf("GET %s HTTP/%s\r\n", path.c_str(), config.http_version); + request += stringtf("User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION); + request += stringtf("Host: %s\r\n", host.c_str()); + if ( strcmp(config.http_version, "1.0") == 0 ) + request += "Connection: Keep-Alive\r\n"; + request += mAuthenticator->getAuthHeader("GET", path.c_str()); + request += "\r\n"; + + Debug(2, "New request header: %s", request.c_str()); + return 0; + } else { + Debug(2, "Need some other kind of Authentication"); + } + } else if (status < 200 || status > 299) { + Error("Invalid response status %s: %s", status_code, status_mesg); + return -1; + } + Debug(3, "Got status '%d' (%s), http version %s", status, status_mesg, http_version); + + if (connection_header) { + memset(connection_type, 0, sizeof(connection_type)); + start_ptr = connection_header + strspn(connection_header, " "); + // FIXME Should we not use strncpy? + strcpy(connection_type, start_ptr); + Debug(3, "Got connection '%s'", connection_type); + } + if (content_length_header) { + start_ptr = content_length_header + strspn(content_length_header, " "); + content_length = atoi(start_ptr); + Debug(3, "Got content length '%d'", content_length); + } + if (content_type_header) { + //memset(content_type, 0, sizeof(content_type)); + start_ptr = content_type_header + strspn(content_type_header, " "); + if ( (end_ptr = strchr(start_ptr, ';')) ) { + strncpy(content_type, start_ptr, end_ptr-start_ptr); + Debug(3, "Got content type '%s'", content_type); + + start_ptr = end_ptr + strspn(end_ptr, "; "); + + if (strncasecmp(start_ptr, boundary_match, boundary_match_len) == 0) { + start_ptr += boundary_match_len; + start_ptr += strspn(start_ptr, "-"); + content_boundary_len = sprintf(content_boundary, "--%s", start_ptr); + Debug(3, "Got content boundary '%s'", content_boundary); + } else { + Error("No content boundary found in header '%s'", content_type_header); + } + } else { + strcpy(content_type, start_ptr); + Debug(3, "Got content type '%s'", content_type); + } + } // end if content_type_header + + if (!strcasecmp(content_type, "image/jpeg") || !strcasecmp(content_type, "image/jpg")) { + // Single image + mode = SINGLE_IMAGE; + format = JPEG; + state = CONTENT; + } else if (!strcasecmp(content_type, "image/x-rgb")) { + // Single image + mode = SINGLE_IMAGE; + format = X_RGB; + state = CONTENT; + } else if (!strcasecmp(content_type, "image/x-rgbz")) { + // Single image + mode = SINGLE_IMAGE; + format = X_RGBZ; + state = CONTENT; + } else if (!strcasecmp(content_type, "multipart/x-mixed-replace")) { + // Image stream, so start processing + if (!content_boundary[0]) { + Error("No content boundary found in header '%s'", content_type_header); + return -1; + } + mode = MULTI_IMAGE; + state = SUBHEADER; + } + //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) + //{ + //// MPEG stream, coming soon! + //} + else { + Error("Unrecognised content type '%s'", content_type); + return -1; + } + } else { + Debug(3, "Unable to extract entire header from stream, continuing"); + state = HEADERCONT; + //return( -1 ); + } // end if all_headers + break; + } + case SUBHEADER : + n_subheaders = 0; + boundary_header = 0; + subcontent_length_header[0] = '\0'; + subcontent_type_header[0] = '\0'; + content_length = 0; + content_type[0] = '\0'; + FALLTHROUGH; + case SUBHEADERCONT : + { + char *crlf = nullptr; + char *subheader_ptr = (char *)buffer; + int subheader_len = buffer.size(); + bool all_headers = false; + + 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); + all_headers = true; + break; + } + } + 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); + } + } + + Debug(6, "%d: %s", subheader_len, subheader_ptr); + + if ( (crlf = mempbrk(subheader_ptr, "\r\n", subheader_len)) ) { + //subheaders[n_subheaders++] = subheader_ptr; + n_subheaders++; + + if ( !boundary_header && (strncasecmp(subheader_ptr, content_boundary, content_boundary_len) == 0) ) { + boundary_header = subheader_ptr; + Debug(4, "Got boundary subheader '%s'", subheader_ptr); + } else if ( + !subcontent_length_header[0] + && + (strncasecmp(subheader_ptr, content_length_match, content_length_match_len) == 0) + ) { + strncpy( + subcontent_length_header, + subheader_ptr+content_length_match_len, + sizeof(subcontent_length_header)-1 + ); + *(subcontent_length_header+strcspn(subcontent_length_header, "\r\n")) = '\0'; + Debug(4, "Got content length subheader '%s'", subcontent_length_header); + } else if ( !subcontent_type_header[0] && (strncasecmp( subheader_ptr, content_type_match, content_type_match_len) == 0) ) { + strncpy( + subcontent_type_header, + subheader_ptr+content_type_match_len, + sizeof(subcontent_type_header)-1 + ); + *(subcontent_type_header+strcspn(subcontent_type_header, "\r\n")) = '\0'; + Debug(4, "Got content type subheader '%s'", subcontent_type_header); + } else { + Debug(6, "Got ignored subheader '%s' found", subheader_ptr); + } + subheader_ptr = crlf; + subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); + } else { + // No line end found + break; } } - Debug( 3, "Returning %d bytes, buffer size: (%d) bytes of captured content", content_length, buffer.size() ); - return( content_length ); - } // end cast CONTENT + if (all_headers && boundary_header) { + char *start_ptr/*, *end_ptr*/; + + 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_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"); + int buffer_len = GetData(); + if (buffer_len < 0) { + Error("Unable to read subheader"); + return -1; + } + bytes += buffer_len; + state = SUBHEADERCONT; + } + break; + } + case CONTENT : { + // if content_type is something like image/jpeg;size=, this will strip the ;size= + char * semicolon = strchr(content_type, ';'); + if (semicolon) { + *semicolon = '\0'; + } + + if (!strcasecmp(content_type, "image/jpeg") || !strcasecmp(content_type, "image/jpg")) { + format = JPEG; + } else if (!strcasecmp(content_type, "image/x-rgb")) { + format = X_RGB; + } else if (!strcasecmp(content_type, "image/x-rgbz")) { + format = X_RGBZ; + } else { + Error("Found unsupported content type '%s'", content_type); + return -1; + } + + // This is an early test for jpeg content, so we can bail early + 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) and !zm_terminate) { + Debug(4, "getting more data"); + int bytes_read = ReadData(buffer, content_length-buffer.size()); + if (bytes_read < 0) { + Error("Unable to read content"); + return -1; + } + bytes += bytes_read; + } + Debug(3, "Got end of image by length, content-length = %d", content_length); + } else { + // Read until we find the end of image or the stream closes. + while (!content_length && !zm_terminate) { + Debug(4, "!content_length, ReadData"); + 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) { + // Got some data + + 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)) { + content_length = start_ptr - (char *)buffer; + 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); + } + } // 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) { + char *end_ptr = (char *)buffer+buffer_size; + + // strip off any last line feeds + 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); + } + } // end if SINGLE_IMAGE + } // end if read some data + } // end while ! content_length + } // end if content_length + + 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; + } + } + + Debug(3, "Returning %d bytes, buffer size: (%d) bytes of captured content", + content_length, buffer.size()); + return content_length; + } // end case CONTENT } // end switch } } - return( 0 ); + return 0; +} // end RemoteCameraHttp::GetResponse + +int RemoteCameraHttp::PrimeCapture() { + if ( sd < 0 ) { + Connect(); + if ( sd < 0 ) { + return -1; + } + mode = SINGLE_IMAGE; + buffer.clear(); + } + getVideoStream(); + return 1; } int RemoteCameraHttp::PreCapture() { if ( sd < 0 ) { Connect(); if ( sd < 0 ) { - Error("Unable to connect to camera"); return -1; } mode = SINGLE_IMAGE; @@ -1069,64 +1073,62 @@ int RemoteCameraHttp::PreCapture() { if ( mode == SINGLE_IMAGE ) { if ( SendRequest() < 0 ) { Error("Unable to send request"); - Disconnect(); return -1; } } - return 0; -} + return 1; +} // end int RemoteCameraHttp::PreCapture() -int RemoteCameraHttp::Capture( Image &image ) { +int RemoteCameraHttp::Capture(std::shared_ptr &packet) { int content_length = GetResponse(); - if ( content_length == 0 ) { - Warning( "Unable to capture image, retrying" ); + if (content_length == 0) { + Warning("Unable to capture image, retrying"); return 0; } - if ( content_length < 0 ) { - Error( "Unable to get response, disconnecting" ); - Disconnect(); + if (content_length < 0) { + Error("Unable to get response, disconnecting"); return -1; } - switch( format ) { + + if (!packet->image) { + Debug(4, "Allocating image"); + packet->image = new Image(width, height, colours, subpixelorder); + } + Image *image = packet->image; + packet->keyframe = 1; + packet->codec_type = AVMEDIA_TYPE_VIDEO; + packet->packet.stream_index = mVideoStreamId; + packet->stream = mVideoStream; + + switch (format) { case JPEG : - { - if ( !image.DecodeJpeg( buffer.extract( content_length ), content_length, colours, subpixelorder ) ) { - Error( "Unable to decode jpeg" ); - Disconnect(); - return -1; - } - break; - } - case X_RGB : - { - if ( content_length != (long)image.Size() ) { - Error( "Image length mismatch, expected %d bytes, content length was %d", image.Size(), content_length ); - Disconnect(); - return -1; - } - image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); - break; - } - case X_RGBZ : - { - if ( !image.Unzip( buffer.extract( content_length ), content_length ) ) { - Error( "Unable to unzip RGB image" ); - Disconnect(); - return -1; - } - image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); - break; - } - default : - { - Error( "Unexpected image format encountered" ); - Disconnect(); + if (!image->DecodeJpeg(buffer.extract(content_length), content_length, colours, subpixelorder)) { + Error("Unable to decode jpeg"); return -1; } + break; + case X_RGB : + if (content_length != (long)image->Size()) { + Error("Image length mismatch, expected %d bytes, content length was %d", + image->Size(), content_length); + return -1; + } + image->Assign(width, height, colours, subpixelorder, buffer.head(), imagesize); + break; + case X_RGBZ : + if (!image->Unzip(buffer.extract(content_length), content_length)) { + Error("Unable to unzip RGB image"); + return -1; + } + image->Assign(width, height, colours, subpixelorder, buffer.head(), imagesize); + break; + default : + Error("Unexpected image format encountered"); + return -1; } return 1; -} +} // end ZmPacket *RmoteCameraHttp::Capture( &image ); int RemoteCameraHttp::PostCapture() { - return 0; + return 1; } diff --git a/src/zm_remote_camera_http.h b/src/zm_remote_camera_http.h index c3be50885..794ccba78 100644 --- a/src/zm_remote_camera_http.h +++ b/src/zm_remote_camera_http.h @@ -20,11 +20,8 @@ #ifndef ZM_REMOTE_CAMERA_HTTP_H #define ZM_REMOTE_CAMERA_HTTP_H -#include "zm_remote_camera.h" - #include "zm_buffer.h" -#include "zm_regexp.h" -#include "zm_utils.h" +#include "zm_remote_camera.h" // // Class representing 'http' cameras, i.e. those which are @@ -44,21 +41,37 @@ protected: enum { SIMPLE, REGEXP } method; public: - RemoteCameraHttp( unsigned int p_monitor_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); + RemoteCameraHttp( + const Monitor *monitor, + const std::string &method, + const std::string &host, + const std::string &port, + const std::string &path, + int p_width, + int p_height, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio + ); ~RemoteCameraHttp(); - void Initialise(); - void Terminate() { Disconnect(); } - int Connect(); - int Disconnect(); + void Initialise() override; + void Terminate() override { Disconnect(); } + int Connect() override; + int Disconnect() override; int SendRequest(); int ReadData( Buffer &buffer, unsigned int bytes_expected=0 ); + int GetData(); int GetResponse(); - int PreCapture(); - int Capture( Image &image ); - int PostCapture(); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return 0;}; - int Close() { Disconnect(); return 0; }; + int PrimeCapture() override; + int PreCapture() override; + int Capture(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 9045b962a..92f806eed 100644 --- a/src/zm_remote_camera_nvsocket.cpp +++ b/src/zm_remote_camera_nvsocket.cpp @@ -19,12 +19,12 @@ #include "zm_remote_camera_nvsocket.h" -#include "zm_mem_utils.h" - -#include -#include -#include +#include "zm_monitor.h" +#include "zm_packet.h" #include +#include +#include +#include #ifdef SOLARIS #include // FIONREAD and friends @@ -34,7 +34,7 @@ #endif RemoteCameraNVSocket::RemoteCameraNVSocket( - unsigned int p_monitor_id, + const Monitor *monitor, const std::string &p_host, const std::string &p_port, const std::string &p_path, @@ -48,7 +48,7 @@ RemoteCameraNVSocket::RemoteCameraNVSocket( bool p_capture, bool p_record_audio ) : RemoteCamera( - p_monitor_id, + monitor, "http", p_host, p_port, @@ -68,6 +68,7 @@ RemoteCameraNVSocket::RemoteCameraNVSocket( timeout.tv_sec = 0; timeout.tv_usec = 0; subpixelorder = ZM_SUBPIX_ORDER_BGR; + mVideoStream = NULL; if ( capture ) { Initialise(); @@ -116,7 +117,7 @@ int RemoteCameraNVSocket::Connect() { close(sd); sd = -1; - Warning("Can't connect to socket mid: %d : %s", monitor_id, strerror(errno) ); + Warning("Can't connect to socket mid: %d : %s", monitor->Id(), strerror(errno)); return -1; } @@ -137,13 +138,13 @@ int RemoteCameraNVSocket::Disconnect() { } int RemoteCameraNVSocket::SendRequest( std::string request ) { - Debug( 4, "Sending request: %s", request.c_str() ); + //Debug( 4, "Sending request: %s", request.c_str() ); if ( write( sd, request.data(), request.length() ) < 0 ) { Error( "Can't write: %s", strerror(errno) ); Disconnect(); return( -1 ); } - Debug( 4, "Request sent" ); + //Debug( 4, "Request sent" ); return( 0 ); } @@ -178,13 +179,14 @@ int RemoteCameraNVSocket::PrimeCapture() { Disconnect(); return -1; } + mVideoStreamId=0; return 0; } -int RemoteCameraNVSocket::Capture( Image &image ) { - 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); @@ -193,16 +195,17 @@ int RemoteCameraNVSocket::Capture( Image &image ) { 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; } - image.Assign(width, height, colours, subpixelorder, buffer, imagesize); + zm_packet->image->Assign(width, height, colours, subpixelorder, buffer, imagesize); + zm_packet->keyframe = 1; return 1; } diff --git a/src/zm_remote_camera_nvsocket.h b/src/zm_remote_camera_nvsocket.h index fbd7fcd86..d3b5ce8e9 100644 --- a/src/zm_remote_camera_nvsocket.h +++ b/src/zm_remote_camera_nvsocket.h @@ -20,28 +20,19 @@ #ifndef ZM_REMOTE_CAMERA_NVSOCKET_H #define ZM_REMOTE_CAMERA_NVSOCKET_H +#include "zm_buffer.h" #include "zm_remote_camera.h" -#include "zm_buffer.h" -#include "zm_regexp.h" -#include "zm_utils.h" - -// -// Class representing 'http' cameras, i.e. those which are -// accessed over a network connection using http -// class RemoteCameraNVSocket : public RemoteCamera { protected: std::string request; struct timeval timeout; - //struct hostent *hp; - //struct sockaddr_in sa; int sd; Buffer buffer; public: RemoteCameraNVSocket( - unsigned int p_monitor_id, + const Monitor *monitor, const std::string &host, const std::string &port, const std::string &path, @@ -56,18 +47,16 @@ public: bool p_record_audio ); ~RemoteCameraNVSocket(); - void Initialise(); - void Terminate() { Disconnect(); } - int Connect(); - int Disconnect(); - int SendRequest( std::string ); - int ReadData( Buffer &buffer, unsigned int bytes_expected=0 ); + void Initialise() override; + void Terminate() override { Disconnect(); } + int Connect() override; + int Disconnect() override; + int SendRequest(std::string); int GetResponse(); - int PrimeCapture(); - int Capture( Image &image ); - int PostCapture(); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; - int Close() { return 0; }; + int PrimeCapture() override; + int Capture(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 582cd5d6b..ffb4a061d 100644 --- a/src/zm_remote_camera_rtsp.cpp +++ b/src/zm_remote_camera_rtsp.cpp @@ -17,19 +17,15 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" - -#if HAVE_LIBAVFORMAT - #include "zm_remote_camera_rtsp.h" -#include "zm_ffmpeg.h" -#include "zm_mem_utils.h" -#include -#include +#include "zm_config.h" +#include "zm_monitor.h" +#include "zm_packet.h" +#include "zm_signal.h" RemoteCameraRtsp::RemoteCameraRtsp( - unsigned int p_monitor_id, + const Monitor *monitor, const std::string &p_method, const std::string &p_host, const std::string &p_port, @@ -44,10 +40,14 @@ RemoteCameraRtsp::RemoteCameraRtsp( int p_colour, bool p_capture, bool p_record_audio ) : - RemoteCamera( p_monitor_id, "rtsp", p_host, p_port, p_path, p_width, p_height, p_colours, p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), - rtsp_describe( p_rtsp_describe ), - rtspThread( 0 ) - + RemoteCamera( + monitor, "rtsp", + p_host, p_port, p_path, + p_width, p_height, p_colours, + p_brightness, p_contrast, p_hue, p_colour, + p_capture, p_record_audio), + rtsp_describe(p_rtsp_describe), + frameCount(0) { if ( p_method == "rtpUni" ) method = RtspThread::RTP_UNICAST; @@ -58,25 +58,12 @@ RemoteCameraRtsp::RemoteCameraRtsp( else if ( p_method == "rtpRtspHttp" ) method = RtspThread::RTP_RTSP_HTTP; else - Fatal("Unrecognised method '%s' when creating RTSP camera %d", p_method.c_str(), monitor_id); + Fatal("Unrecognised method '%s' when creating RTSP camera %d", p_method.c_str(), monitor->Id()); if ( capture ) { Initialise(); } - mFormatContext = NULL; - mVideoStreamId = -1; - mAudioStreamId = -1; - mCodecContext = NULL; - mCodec = NULL; - mRawFrame = NULL; - mFrame = NULL; - frameCount = 0; - startTime=0; - -#if HAVE_LIBSWSCALE - mConvertContext = NULL; -#endif /* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */ if ( colours == ZM_COLOUR_RGB32 ) { subpixelorder = ZM_SUBPIX_ORDER_RGBA; @@ -93,20 +80,13 @@ RemoteCameraRtsp::RemoteCameraRtsp( } // end RemoteCameraRtsp::RemoteCameraRtsp(...) RemoteCameraRtsp::~RemoteCameraRtsp() { - av_frame_free(&mFrame); - av_frame_free(&mRawFrame); - -#if HAVE_LIBSWSCALE - if ( mConvertContext ) { - sws_freeContext(mConvertContext); - mConvertContext = NULL; - } -#endif - if ( mCodecContext ) { - avcodec_close(mCodecContext); - mCodecContext = NULL; // Freed by avformat_free_context in the destructor of RtspThread class + if ( mVideoCodecContext ) { + avcodec_close(mVideoCodecContext); + mVideoCodecContext = nullptr; // Freed by avformat_free_context in the destructor of RtspThread class } + // Is allocated in RTSPThread and is free there as well + mFormatContext = nullptr; if ( capture ) { Terminate(); @@ -132,30 +112,29 @@ 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 = 0; + 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 = 100; i && !zm_terminate && !rtspThread->hasSources(); i--) { + std::this_thread::sleep_for(Microseconds(10000)); + } + + if (!rtspThread->hasSources()) { + Error("No RTSP sources"); + return -1; } - if ( !rtspThread->hasSources() ) - Fatal("No RTSP sources"); Debug(2, "Got sources"); @@ -164,144 +143,99 @@ int RemoteCameraRtsp::PrimeCapture() { // Find first video stream present mVideoStreamId = -1; mAudioStreamId = -1; - + // Find the first video stream. for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) { -#if (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 ( is_video_stream(mFormatContext->streams[i]) ) { if ( mVideoStreamId == -1 ) { mVideoStreamId = i; + mVideoStream = mFormatContext->streams[i]; + mVideoStream->time_base = AV_TIME_BASE_Q; continue; } else { Debug(2, "Have another video stream."); } - } else -#if (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_AUDIO ) -#else - if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO ) -#endif - { +#if 0 + } else if ( is_audio_stream(mFormatContext->streams[i]) ) { if ( mAudioStreamId == -1 ) { mAudioStreamId = i; + mAudioStream = mFormatContext->streams[i]; } else { Debug(2, "Have another audio stream."); } +#endif } else { - Debug(1, "Have unknown codec type in stream %d : %d", i, mFormatContext->streams[i]->codec->codec_type); + Debug(1, "Have unknown codec type in stream %d", i); } } // end foreach stream - if ( mVideoStreamId == -1 ) - Fatal("Unable to locate video stream"); + if ( mVideoStreamId == -1 ) { + Error("Unable to locate video stream"); + return -1; + } if ( mAudioStreamId == -1 ) Debug(3, "Unable to locate audio stream"); // Get a pointer to the codec context for the video stream - mCodecContext = mFormatContext->streams[mVideoStreamId]->codec; + mVideoCodecContext = avcodec_alloc_context3(nullptr); + avcodec_parameters_to_context(mVideoCodecContext, mFormatContext->streams[mVideoStreamId]->codecpar); // Find the decoder for the video stream - mCodec = avcodec_find_decoder(mCodecContext->codec_id); - if ( mCodec == NULL ) - Panic("Unable to locate codec %d decoder", mCodecContext->codec_id); + AVCodec *codec = avcodec_find_decoder(mVideoCodecContext->codec_id); + if ( codec == nullptr ) { + Error("Unable to locate codec %d decoder", mVideoCodecContext->codec_id); + return -1; + } // Open codec -#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) - if ( avcodec_open(mCodecContext, mCodec) < 0 ) -#else - if ( avcodec_open2(mCodecContext, mCodec, 0) < 0 ) -#endif - Panic("Can't open codec"); + if ( avcodec_open2(mVideoCodecContext, codec, nullptr) < 0 ) { + Error("Can't open codec"); + return -1; + } - // Allocate space for the native video frame -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - mRawFrame = av_frame_alloc(); -#else - mRawFrame = avcodec_alloc_frame(); -#endif - - // Allocate space for the converted video frame -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - mFrame = av_frame_alloc(); -#else - mFrame = avcodec_alloc_frame(); -#endif - - if ( mRawFrame == NULL || mFrame == NULL ) - Fatal("Unable to allocate frame(s)"); - -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1); -#else - int pSize = avpicture_get_size(imagePixFormat, width, height); -#endif if ( (unsigned int)pSize != imagesize ) { - Fatal("Image size mismatch. Required: %d Available: %d", pSize, imagesize); - } -/* -#if HAVE_LIBSWSCALE - if(!sws_isSupportedInput(mCodecContext->pix_fmt)) { - Fatal("swscale does not support the codec format: %c%c%c%c",(mCodecContext->pix_fmt)&0xff,((mCodecContext->pix_fmt>>8)&0xff),((mCodecContext->pix_fmt>>16)&0xff),((mCodecContext->pix_fmt>>24)&0xff)); + Error("Image size mismatch. Required: %d Available: %llu", pSize, imagesize); + return -1; } - if(!sws_isSupportedOutput(imagePixFormat)) { - Fatal("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff)); - } - -#else // HAVE_LIBSWSCALE - Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" ); -#endif // HAVE_LIBSWSCALE -*/ - - return 0; -} + return 1; +} // end PrimeCapture int RemoteCameraRtsp::PreCapture() { - if ( !rtspThread->isRunning() ) + if (!rtspThread || rtspThread->IsStopped()) return -1; if ( !rtspThread->hasSources() ) { Error("Cannot precapture, no RTP sources"); return -1; } - return 0; + return 1; } -int RemoteCameraRtsp::Capture( Image &image ) { - AVPacket packet; - uint8_t* directbuffer; +int RemoteCameraRtsp::Capture(std::shared_ptr &zm_packet) { int frameComplete = false; - - /* Request a writeable buffer of the target image */ - directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); - if ( directbuffer == NULL ) { - Error("Failed requesting writeable buffer for the captured image."); - return -1; - } - - while ( true ) { + AVPacket *packet = &zm_packet->packet; + + while (!frameComplete) { buffer.clear(); - if ( !rtspThread->isRunning() ) + if (!rtspThread || rtspThread->IsStopped() || zm_terminate) return -1; - if ( rtspThread->getFrame(buffer) ) { + if (rtspThread->getFrame(buffer)) { Debug(3, "Read frame %d bytes", buffer.size()); - Debug(4, "Address %p", buffer.head()); Hexdump(4, buffer.head(), 16); if ( !buffer.size() ) return -1; - if ( mCodecContext->codec_id == AV_CODEC_ID_H264 ) { + if ( mVideoCodecContext->codec_id == AV_CODEC_ID_H264 ) { // SPS and PPS frames should be saved and appended to IDR frames int nalType = (buffer.head()[3] & 0x1f); // SPS The SPS NAL unit contains parameters that apply to a series of consecutive coded video pictures - if ( nalType == 7 ) { + if ( nalType == 1 ) { + } else if ( nalType == 7 ) { lastSps = buffer; continue; } else if ( nalType == 8 ) { @@ -309,91 +243,41 @@ int RemoteCameraRtsp::Capture( Image &image ) { lastPps = buffer; continue; } else if ( nalType == 5 ) { + packet->flags |= AV_PKT_FLAG_KEY; + zm_packet->keyframe = 1; // IDR buffer += lastSps; buffer += lastPps; + } else { + Debug(2, "Unknown nalType %d", nalType); } } else { Debug(3, "Not an h264 packet"); } - av_init_packet(&packet); + //while ( (!frameComplete) && (buffer.size() > 0) ) { + if ( buffer.size() > 0 ) { + packet->data = (uint8_t*)av_malloc(buffer.size()); + memcpy(packet->data, buffer.head(), buffer.size()); + //packet->data = buffer.head(); + packet->size = buffer.size(); + bytes += packet->size; + buffer -= packet->size; - while ( !frameComplete && (buffer.size() > 0) ) { - packet.data = buffer.head(); - packet.size = buffer.size(); - bytes += packet.size; - - // So I think this is the magic decode step. Result is a raw image? -#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) - int len = avcodec_decode_video2(mCodecContext, mRawFrame, &frameComplete, &packet); -#else - int len = avcodec_decode_video(mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size); -#endif - if ( len < 0 ) { - Error("Error while decoding frame %d", frameCount); - Hexdump(Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size()); - buffer.clear(); - continue; - } - Debug(2, "Frame: %d - %d/%d", frameCount, len, buffer.size()); - //if ( buffer.size() < 400 ) - //Hexdump( 0, buffer.head(), buffer.size() ); - - buffer -= len; + struct timeval now; + gettimeofday(&now, nullptr); + packet->pts = packet->dts = now.tv_sec*1000000+now.tv_usec; + zm_packet->codec_type = mVideoCodecContext->codec_type; + zm_packet->stream = mVideoStream; + frameComplete = true; + Debug(2, "Frame: %d - %d/%d", frameCount, packet->size, buffer.size()); } - // At this point, we either have a frame or ran out of buffer. What happens if we run out of buffer? - if ( frameComplete ) { - - Debug(3, "Got frame %d", frameCount); - - avpicture_fill((AVPicture *)mFrame, directbuffer, imagePixFormat, width, height); - - #if HAVE_LIBSWSCALE - if ( mConvertContext == NULL ) { - mConvertContext = sws_getContext( - mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, - width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL); - - if ( mConvertContext == NULL ) - Fatal("Unable to create conversion context"); - - if ( - ((unsigned int)mRawFrame->width != width) - || - ((unsigned int)mRawFrame->height != height) - ) { - Warning("Monitor dimensions are %dx%d but camera is sending %dx%d", - width, height, mRawFrame->width, mRawFrame->height); - } - } - - if ( sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize) < 0 ) - Fatal("Unable to convert raw format %u to target format %u at frame %d", - mCodecContext->pix_fmt, imagePixFormat, frameCount ); - #else // HAVE_LIBSWSCALE - Fatal("You must compile ffmpeg with the --enable-swscale option to use RTSP cameras"); - #endif // HAVE_LIBSWSCALE - - frameCount++; - - } /* frame complete */ - - zm_av_packet_unref(&packet); } /* getFrame() */ - - if ( frameComplete ) - return 1; - } // end while true - // can never get here. - return 0; -} - -//Function to handle capture and store + return 1; +} // end int RemoteCameraRtsp::Capture(ZMPacket &packet) int RemoteCameraRtsp::PostCapture() { - return 0; + return 1; } -#endif // HAVE_LIBAVFORMAT diff --git a/src/zm_remote_camera_rtsp.h b/src/zm_remote_camera_rtsp.h index be7542a84..4880f6f35 100644 --- a/src/zm_remote_camera_rtsp.h +++ b/src/zm_remote_camera_rtsp.h @@ -20,14 +20,9 @@ #ifndef ZM_REMOTE_CAMERA_RTSP_H #define ZM_REMOTE_CAMERA_RTSP_H -#include "zm_remote_camera.h" - -#include "zm_buffer.h" -#include "zm_utils.h" -#include "zm_rtsp.h" #include "zm_ffmpeg.h" -#include "zm_videostore.h" -#include "zm_packetqueue.h" +#include "zm_remote_camera.h" +#include "zm_rtsp.h" // // Class representing 'rtsp' cameras, i.e. those which are @@ -49,44 +44,55 @@ protected: RtspThread::RtspMethod method; - RtspThread *rtspThread; + std::unique_ptr rtspThread; int frameCount; -#if HAVE_LIBAVFORMAT AVFormatContext *mFormatContext; - int mVideoStreamId; - int mAudioStreamId; - AVCodecContext *mCodecContext; - AVCodec *mCodec; - AVFrame *mRawFrame; - AVFrame *mFrame; _AVPIXELFORMAT imagePixFormat; -#endif // HAVE_LIBAVFORMAT - bool wasRecording; - VideoStore *videoStore; - char oldDirectory[4096]; - int64_t startTime; - -#if HAVE_LIBSWSCALE - struct SwsContext *mConvertContext; -#endif public: - RemoteCameraRtsp( unsigned int p_monitor_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); + RemoteCameraRtsp( + const Monitor *monitor, + const std::string &method, + const std::string &host, + const std::string &port, + const std::string &path, + int p_width, + int p_height, + bool p_rtsp_describe, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio); ~RemoteCameraRtsp(); - void Initialise(); - void Terminate(); - int Connect(); - int Disconnect(); + void Initialise() override; + void Terminate() override; + int Connect() override; + int Disconnect() override; - int PrimeCapture(); - int PreCapture(); - int Capture( Image &image ); - int PostCapture(); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return 0;}; - int Close() { return 0; }; + int PrimeCapture() override; + int PreCapture() override; + int Capture(std::shared_ptr &p) override; + int PostCapture() override; + int Close() override { return 0; }; + + AVStream *get_VideoStream() { + if ( mVideoStreamId != -1 ) + return mFormatContext->streams[mVideoStreamId]; + return nullptr; + } + AVStream *get_AudioStream() { + if ( mAudioStreamId != -1 ) + return mFormatContext->streams[mAudioStreamId]; + return nullptr; + } + AVCodecContext *get_VideoCodecContext() { return mVideoCodecContext; }; + AVCodecContext *get_AudioCodecContext() { return mAudioCodecContext; }; }; #endif // ZM_REMOTE_CAMERA_RTSP_H diff --git a/src/zm_rgb.h b/src/zm_rgb.h index 1b6cb7550..ef1d46ce3 100644 --- a/src/zm_rgb.h +++ b/src/zm_rgb.h @@ -20,26 +20,28 @@ #ifndef ZM_RGB_H #define ZM_RGB_H -typedef uint32_t Rgb; // RGB colour type +#include "zm_define.h" -#define WHITE 0xff -#define WHITE_R 0xff -#define WHITE_G 0xff -#define WHITE_B 0xff +typedef uint32 Rgb; // RGB colour type -#define BLACK 0x00 -#define BLACK_R 0x00 -#define BLACK_G 0x00 -#define BLACK_B 0x00 +constexpr uint8 kWhite = 0xff; +constexpr uint8 kWhiteR = 0xff; +constexpr uint8 kWhiteG = 0xff; +constexpr uint8 kWhiteB = 0xff; -#define RGB_WHITE (0x00ffffff) -#define RGB_BLACK (0x00000000) -#define RGB_RED (0x000000ff) -#define RGB_GREEN (0x0000ff00) -#define RGB_BLUE (0x00ff0000) -#define RGB_ORANGE (0x0000a5ff) -#define RGB_PURPLE (0x00800080) -#define RGB_TRANSPARENT (0x01000000) +constexpr uint8 kBlack = 0x00; +constexpr uint8 kBlackR = 0x00; +constexpr uint8 kBlackG = 0x00; +constexpr uint8 kBlackB = 0x00; + +constexpr Rgb kRGBWhite = 0x00ffffff; +constexpr Rgb kRGBBlack = 0x00000000; +constexpr Rgb kRGBRed = 0x000000ff; +constexpr Rgb kRGBGreen = 0x0000ff00; +constexpr Rgb kRGBBlue = 0x00ff0000; +constexpr Rgb kRGBOrange = 0x0000a5ff; +constexpr Rgb kRGBPurple = 0x00800080; +constexpr Rgb kRGBTransparent = 0x01000000; #define RGB_VAL(v,c) (((v)>>(16-((c)*8)))&0xff) @@ -116,41 +118,34 @@ typedef uint32_t Rgb; // RGB colour type /* Convert RGB colour value into BGR\ARGB\ABGR */ inline Rgb rgb_convert(Rgb p_col, int p_subpixorder) { - Rgb result; + Rgb result = 0; - switch(p_subpixorder) { - + switch (p_subpixorder) { case ZM_SUBPIX_ORDER_BGR: case ZM_SUBPIX_ORDER_BGRA: - { - BLUE_PTR_BGRA(&result) = BLUE_VAL_RGBA(p_col); - GREEN_PTR_BGRA(&result) = GREEN_VAL_RGBA(p_col); - RED_PTR_BGRA(&result) = RED_VAL_RGBA(p_col); - } - break; + BLUE_PTR_BGRA(&result) = BLUE_VAL_RGBA(p_col); + GREEN_PTR_BGRA(&result) = GREEN_VAL_RGBA(p_col); + RED_PTR_BGRA(&result) = RED_VAL_RGBA(p_col); + break; case ZM_SUBPIX_ORDER_ARGB: - { - BLUE_PTR_ARGB(&result) = BLUE_VAL_RGBA(p_col); - GREEN_PTR_ARGB(&result) = GREEN_VAL_RGBA(p_col); - RED_PTR_ARGB(&result) = RED_VAL_RGBA(p_col); - } - break; + BLUE_PTR_ARGB(&result) = BLUE_VAL_RGBA(p_col); + GREEN_PTR_ARGB(&result) = GREEN_VAL_RGBA(p_col); + RED_PTR_ARGB(&result) = RED_VAL_RGBA(p_col); + break; case ZM_SUBPIX_ORDER_ABGR: - { - BLUE_PTR_ABGR(&result) = BLUE_VAL_RGBA(p_col); - GREEN_PTR_ABGR(&result) = GREEN_VAL_RGBA(p_col); - RED_PTR_ABGR(&result) = RED_VAL_RGBA(p_col); - } - break; - /* Grayscale */ + BLUE_PTR_ABGR(&result) = BLUE_VAL_RGBA(p_col); + GREEN_PTR_ABGR(&result) = GREEN_VAL_RGBA(p_col); + RED_PTR_ABGR(&result) = RED_VAL_RGBA(p_col); + break; + /* Grayscale */ case ZM_SUBPIX_ORDER_NONE: - result = p_col & 0xff; - break; + result = p_col & 0xff; + break; default: - return p_col; - break; + result = p_col; + break; } - + return result; } diff --git a/src/zm_rtp.h b/src/zm_rtp.h index 25403f1a5..f45e14c74 100644 --- a/src/zm_rtp.h +++ b/src/zm_rtp.h @@ -20,8 +20,6 @@ #ifndef ZM_RTP_H #define ZM_RTP_H -#include "zm.h" - #define RTP_VERSION 2 #endif // ZM_RTP_H diff --git a/src/zm_rtp_ctrl.cpp b/src/zm_rtp_ctrl.cpp index bc6725c5d..a82ff2b2a 100644 --- a/src/zm_rtp_ctrl.cpp +++ b/src/zm_rtp_ctrl.cpp @@ -15,22 +15,24 @@ // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// - -#include "zm.h" - -#if HAVE_LIBAVFORMAT +// #include "zm_rtp_ctrl.h" -#include "zm_time.h" +#include "zm_config.h" +#include "zm_rtp.h" #include "zm_rtsp.h" -#include - -RtpCtrlThread::RtpCtrlThread( RtspThread &rtspThread, RtpSource &rtpSource ) - : mRtspThread( rtspThread ), mRtpSource( rtpSource ), mStop( false ) +RtpCtrlThread::RtpCtrlThread(RtspThread &rtspThread, RtpSource &rtpSource) + : mRtspThread(rtspThread), mRtpSource(rtpSource), mTerminate(false) { + mThread = std::thread(&RtpCtrlThread::Run, this); +} + +RtpCtrlThread::~RtpCtrlThread() { + Stop(); + if (mThread.joinable()) + mThread.join(); } int RtpCtrlThread::recvPacket( const unsigned char *packet, ssize_t packetLen ) { @@ -124,7 +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 @@ -244,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() ); - SockAddrInet localAddr, remoteAddr; + zm::SockAddrInet localAddr, remoteAddr; bool sendReports; - UdpInetSocket rtpCtrlServer; + zm::UdpInetSocket rtpCtrlServer; if ( mRtpSource.getLocalHost() != "" ) { if ( !rtpCtrlServer.bind( mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() ) ) Fatal( "Failed to bind RTCP server" ); @@ -267,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 - Select select( 10 ); + zm::Select select(Seconds(10)); select.addReader( &rtpCtrlServer ); unsigned char buffer[ZM_NETWORK_BUFSIZ]; - time_t last_receive = time(NULL); - 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(NULL); - 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 @@ -286,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(NULL); + last_receive = std::chrono::steady_clock::now(); } - for ( Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) { - if ( UdpInetSocket *socket = dynamic_cast(*iter) ) { + for (zm::Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) { + if ( zm::UdpInetSocket *socket = dynamic_cast(*iter) ) { ssize_t nBytes = socket->recv( buffer, sizeof(buffer) ); Debug( 4, "Read %zd bytes on sd %d", nBytes, socket->getReadDesc() ); @@ -321,7 +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 { @@ -330,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 9e5306f92..9346496f7 100644 --- a/src/zm_rtp_ctrl.h +++ b/src/zm_rtp_ctrl.h @@ -20,9 +20,9 @@ #ifndef ZM_RTP_CTRL_H #define ZM_RTP_CTRL_H -#include "zm_rtp.h" -#include "zm_comms.h" -#include "zm_thread.h" +#include +#include +#include // Defined in ffmpeg rtp.h //#define RTP_MAX_SDES 255 // maximum text length for SDES @@ -34,7 +34,7 @@ class RtspThread; class RtpSource; -class RtpCtrlThread : public Thread { +class RtpCtrlThread { friend class RtspThread; private: @@ -123,7 +123,9 @@ private: RtspThread &mRtspThread; RtpSource &mRtpSource; int mPort; - bool mStop; + + std::atomic mTerminate; + std::thread mThread; private: int recvPacket( const unsigned char *packet, ssize_t packetLen ); @@ -131,14 +133,13 @@ private: int generateSdes( const unsigned char *packet, ssize_t packetLen ); int generateBye( const unsigned char *packet, ssize_t packetLen ); int recvPackets( unsigned char *buffer, ssize_t nBytes ); - int run(); + void Run(); public: RtpCtrlThread( RtspThread &rtspThread, RtpSource &rtpSource ); + ~RtpCtrlThread(); - void stop() { - mStop = true; - } + void Stop() { mTerminate = true; } }; #endif // ZM_RTP_CTRL_H diff --git a/src/zm_rtp_data.cpp b/src/zm_rtp_data.cpp index 4fbf0cd04..e8ce628f2 100644 --- a/src/zm_rtp_data.cpp +++ b/src/zm_rtp_data.cpp @@ -17,108 +17,91 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" - -#if HAVE_LIBAVFORMAT - #include "zm_rtp_data.h" +#include "zm_config.h" #include "zm_rtsp.h" +#include "zm_signal.h" -#include - -RtpDataThread::RtpDataThread( RtspThread &rtspThread, RtpSource &rtpSource ) : mRtspThread( rtspThread ), mRtpSource( rtpSource ), mStop( false ) +RtpDataThread::RtpDataThread(RtspThread &rtspThread, RtpSource &rtpSource) : + mRtspThread(rtspThread), mRtpSource(rtpSource), mTerminate(false) { + mThread = std::thread(&RtpDataThread::Run, this); } -bool RtpDataThread::recvPacket( const unsigned char *packet, size_t packetLen ) -{ +RtpDataThread::~RtpDataThread() { + Stop(); + if (mThread.joinable()) + mThread.join(); +} + +bool RtpDataThread::recvPacket(const unsigned char *packet, size_t packetLen) { const RtpDataHeader *rtpHeader; rtpHeader = (RtpDataHeader *)packet; - //printf( "D: " ); - //for ( int i = 0; i < 32; i++ ) - //printf( "%02x ", (unsigned char)packet[i] ); - //printf( "\n" ); - - Debug( 5, "Ver: %d", rtpHeader->version ); - Debug( 5, "P: %d", rtpHeader->p ); - Debug( 5, "Pt: %d", rtpHeader->pt ); - Debug( 5, "Mk: %d", rtpHeader->m ); - Debug( 5, "Seq: %d", ntohs(rtpHeader->seqN) ); - Debug( 5, "T/S: %x", ntohl(rtpHeader->timestampN) ); - Debug( 5, "SSRC: %x", ntohl(rtpHeader->ssrcN) ); + Debug(5, "Ver: %d P: %d Pt: %d Mk: %d Seq: %d T/S: %x SSRC: %x", + rtpHeader->version, + rtpHeader->p, + rtpHeader->pt, + rtpHeader->m, + ntohs(rtpHeader->seqN), + ntohl(rtpHeader->timestampN), + ntohl(rtpHeader->ssrcN)); //unsigned short seq = ntohs(rtpHeader->seqN); unsigned long ssrc = ntohl(rtpHeader->ssrcN); - if ( mRtpSource.getSsrc() && (ssrc != mRtpSource.getSsrc()) ) - { - Warning( "Discarding packet for unrecognised ssrc %lx", ssrc ); - return( false ); + if ( mRtpSource.getSsrc() && (ssrc != mRtpSource.getSsrc()) ) { + Warning("Discarding packet for unrecognised ssrc %lx", ssrc); + return false; } - return( mRtpSource.handlePacket( packet, packetLen ) ); + return mRtpSource.handlePacket(packet, packetLen); } -int RtpDataThread::run() -{ - Debug( 2, "Starting data thread %d on port %d", mRtpSource.getSsrc(), mRtpSource.getLocalDataPort() ); +void RtpDataThread::Run() { + Debug(2, "Starting data thread %d on port %d", + mRtpSource.getSsrc(), mRtpSource.getLocalDataPort()); - SockAddrInet localAddr; - UdpInetServer rtpDataSocket; + zm::SockAddrInet localAddr; + zm::UdpInetServer rtpDataSocket; if ( mRtpSource.getLocalHost() != "" ) { - if ( !rtpDataSocket.bind( mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort() ) ) - Fatal( "Failed to bind RTP server" ); - Debug( 3, "Bound to %s:%d", mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort() ); - } - else - { - if ( !rtpDataSocket.bind( mRtspThread.getAddressFamily() == AF_INET6 ? "::" : "0.0.0.0", mRtpSource.getLocalDataPort() ) ) - Fatal( "Failed to bind RTP server" ); - Debug( 3, "Bound to %s:%d", mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort() ); + if ( !rtpDataSocket.bind(mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort()) ) + Fatal("Failed to bind RTP server"); + } else { + if ( !rtpDataSocket.bind( + mRtspThread.getAddressFamily() == AF_INET6 ? "::" : "0.0.0.0", + mRtpSource.getLocalDataPort() ) ) + Fatal("Failed to bind RTP server"); } + Debug(3, "Bound to %s:%d", mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort()); - Select select( 3 ); - select.addReader( &rtpDataSocket ); + zm::Select select(Seconds(3)); + select.addReader(&rtpDataSocket); unsigned char buffer[ZM_NETWORK_BUFSIZ]; - while ( !mStop && select.wait() >= 0 ) - { - if ( mStop ) - break; - Select::CommsList readable = select.getReadable(); - if ( readable.size() == 0 ) - { - Error( "RTP timed out" ); - mStop = true; + while ( !zm_terminate && !mTerminate && (select.wait() >= 0) ) { + zm::Select::CommsList readable = select.getReadable(); + if ( readable.size() == 0 ) { + Error("RTP timed out"); + Stop(); break; } - for ( Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) - { - if ( 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; + 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 { + Stop(); break; } + } else { + Panic("Barfed"); } - else - { - Panic( "Barfed" ); - } - } + } // end foreach commsList } rtpDataSocket.close(); - mRtspThread.stop(); - return( 0 ); + mRtspThread.Stop(); } - -#endif // HAVE_LIBAVFORMAT diff --git a/src/zm_rtp_data.h b/src/zm_rtp_data.h index dae449ea5..e20878212 100644 --- a/src/zm_rtp_data.h +++ b/src/zm_rtp_data.h @@ -20,10 +20,9 @@ #ifndef ZM_RTP_DATA_H #define ZM_RTP_DATA_H -#include "zm_thread.h" -#include "zm_buffer.h" - -#include +#include "zm_define.h" +#include +#include class RtspThread; class RtpSource; @@ -42,26 +41,26 @@ struct RtpDataHeader uint32_t csrc[]; // optional CSRC list }; -class RtpDataThread : public Thread +class RtpDataThread { friend class RtspThread; private: RtspThread &mRtspThread; RtpSource &mRtpSource; - bool mStop; + + std::atomic mTerminate; + std::thread mThread; private: bool recvPacket( const unsigned char *packet, size_t packetLen ); - int run(); + void Run(); public: RtpDataThread( RtspThread &rtspThread, RtpSource &rtpSource ); + ~RtpDataThread(); - void stop() - { - mStop = true; - } + void Stop() { mTerminate = true; } }; #endif // ZM_RTP_DATA_H diff --git a/src/zm_rtp_source.cpp b/src/zm_rtp_source.cpp index 131d32d7e..56ca2cf0d 100644 --- a/src/zm_rtp_source.cpp +++ b/src/zm_rtp_source.cpp @@ -21,10 +21,9 @@ #include "zm_time.h" #include "zm_rtp_data.h" - +#include "zm_utils.h" #include - -#if HAVE_LIBAVCODEC +#include RtpSource::RtpSource( int id, @@ -37,25 +36,27 @@ RtpSource::RtpSource( uint32_t rtpClock, uint32_t rtpTime, _AVCODECID codecId ) : - mId( id ), - mSsrc( ssrc ), - mLocalHost( localHost ), - mRemoteHost( remoteHost ), - mRtpClock( rtpClock ), - mCodecId( codecId ), - mFrame( 65536 ), - mFrameCount( 0 ), - mFrameGood( true ), - mFrameReady( false ), - mFrameProcessed( false ) + mId(id), + mSsrc(ssrc), + mLocalHost(localHost), + mRemoteHost(remoteHost), + mRtpClock(rtpClock), + mCodecId(codecId), + mFrame(65536), + mFrameCount(0), + mFrameGood(true), + prevM(false), + mFrameReady(false), + mFrameProcessed(false), + mTerminate(false) { char hostname[256] = ""; - gethostname( hostname, sizeof(hostname) ); + gethostname(hostname, sizeof(hostname)); - mCname = stringtf( "zm-%d@%s", mId, hostname ); - Debug( 3, "RTP CName = %s", mCname.c_str() ); + mCname = stringtf("zm-%d@%s", mId, hostname); + Debug(3, "RTP CName = %s", mCname.c_str()); - init( seq ); + init(seq); mMaxSeq = seq - 1; mProbation = MIN_SEQUENTIAL; @@ -67,19 +68,25 @@ 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); } -void RtpSource::init( uint16_t seq ) { +RtpSource::~RtpSource() { + mTerminate = true; + mFrameReadyCv.notify_all(); + mFrameProcessedCv.notify_all(); +} + +void RtpSource::init(uint16_t seq) { Debug(3, "Initialising sequence"); mBaseSeq = seq; mMaxSeq = seq; @@ -93,36 +100,36 @@ void RtpSource::init( uint16_t seq ) { mTransit = 0; } -bool RtpSource::updateSeq( uint16_t seq ) { +bool RtpSource::updateSeq(uint16_t seq) { uint16_t uDelta = seq - mMaxSeq; // Source is not valid until MIN_SEQUENTIAL packets with // sequential sequence numbers have been received. - Debug( 5, "Seq: %d", seq ); + Debug(5, "Seq: %d", seq); if ( mProbation) { // packet is in sequence - if ( seq == mMaxSeq + 1) { - Debug( 3, "Sequence in probation %d, in sequence", mProbation ); + if ( seq == mMaxSeq + 1 ) { + Debug(3, "Sequence in probation %d, in sequence", mProbation); mProbation--; mMaxSeq = seq; if ( mProbation == 0 ) { - init( seq ); + init(seq); mReceivedPackets++; - return( true ); + return true; } } else { - Warning( "Sequence in probation %d, out of sequence", mProbation ); + Warning("Sequence in probation %d, out of sequence", mProbation); mProbation = MIN_SEQUENTIAL - 1; mMaxSeq = seq; - return( false ); + return false; } - return( true ); + return true; } else if ( uDelta < MAX_DROPOUT ) { if ( uDelta == 1 ) { - Debug( 4, "Packet in sequence, gap %d", uDelta ); + Debug(4, "Packet in sequence, gap %d", uDelta); } else { - Warning( "Packet in sequence, gap %d", uDelta ); + Warning("Packet in sequence, gap %d", uDelta); } // in order, with permissible gap @@ -132,40 +139,46 @@ bool RtpSource::updateSeq( uint16_t seq ) { } mMaxSeq = seq; } else if ( uDelta <= RTP_SEQ_MOD - MAX_MISORDER ) { - Warning( "Packet out of sequence, gap %d", uDelta ); + Warning("Packet out of sequence, gap %d", uDelta); // the sequence number made a very large jump if ( seq == mBadSeq ) { - Debug( 3, "Restarting sequence" ); + Debug(3, "Restarting sequence"); // Two sequential packets -- assume that the other side // restarted without telling us so just re-sync // (i.e., pretend this was the first packet). - init( seq ); + init(seq); } else { mBadSeq = (seq + 1) & (RTP_SEQ_MOD-1); - return( false ); + return false; } } else { - Warning( "Packet duplicate or reordered, gap %d", uDelta ); + Warning("Packet duplicate or reordered, gap %d", uDelta); // duplicate or reordered packet - return( false ); + return false; } mReceivedPackets++; return( uDelta==1?true:false ); } void RtpSource::updateJitter( const RtpDataHeader *header ) { - if ( mRtpFactor > 0 ) { - Debug( 5, "Delta rtp = %.6f", tvDiffSec( mBaseTimeReal ) ); - uint32_t localTimeRtp = mBaseTimeRtp + uint32_t( tvDiffSec( mBaseTimeReal ) * mRtpFactor ); - Debug( 5, "Local RTP time = %x", localTimeRtp ); - Debug( 5, "Packet RTP time = %x", ntohl(header->timestampN) ); + 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, "Packet transit RTP time = %x", 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 int d = packetTransit - mTransit; - Debug( 5, "Jitter D = %d", d ); + Debug(5, "Jitter D = %d", d); if ( d < 0 ) d = -d; //mJitter += (1./16.) * ((double)d - mJitter); @@ -175,32 +188,36 @@ void RtpSource::updateJitter( const RtpDataHeader *header ) { } else { mJitter = 0; } - Debug( 5, "RTP Jitter: %d", mJitter ); + Debug(5, "RTP Jitter: %d", mJitter); } -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)) ); +void RtpSource::updateRtcpData( + uint32_t ntpTimeSecs, + uint32_t ntpTimeFrac, + uint32_t rtpTime) { + 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 ); + 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 ) { - Debug( 5, "lastSrNtpTime: %ld.%06ld, rtpTime: %x", mLastSrTimeNtp.tv_sec, mLastSrTimeNtp.tv_usec, rtpTime ); - Debug( 5, "ntpTime: %ld.%06ld, rtpTime: %x", ntpTime.tv_sec, ntpTime.tv_usec, rtpTime ); + Debug(5, "lastSrNtpTime: %ld.%06ld, rtpTime: %x" + "ntpTime: %ld.%06ld, rtpTime: %x", + mLastSrTimeNtp.tv_sec, mLastSrTimeNtp.tv_usec, rtpTime, + ntpTime.tv_sec, ntpTime.tv_usec, rtpTime); + + FPSeconds diffNtpTime = + zm::chrono::duration_cast(ntpTime) - zm::chrono::duration_cast(mBaseTimeNtp); - double diffNtpTime = tvDiffSec( mBaseTimeNtp, ntpTime ); uint32_t diffRtpTime = rtpTime - mBaseTimeRtp; + mRtpFactor = static_cast(diffRtpTime / diffNtpTime.count()); - //Debug( 5, "Real-diff: %.6f", diffRealTime ); - Debug( 5, "NTP-diff: %.6f", diffNtpTime ); - Debug( 5, "RTP-diff: %d", diffRtpTime ); - - mRtpFactor = (uint32_t)(diffRtpTime / diffNtpTime); - - Debug( 5, "RTPfactor: %d", mRtpFactor ); + Debug( 5, "NTP-diff: %.6f RTP-diff: %d RTPfactor: %d", + diffNtpTime.count(), diffRtpTime, mRtpFactor); } mLastSrTimeNtpSecs = ntpTimeSecs; mLastSrTimeNtpFrac = ntpTimeFrac; @@ -211,31 +228,31 @@ void RtpSource::updateRtcpData( uint32_t ntpTimeSecs, uint32_t ntpTimeFrac, uint void RtpSource::updateRtcpStats() { uint32_t extendedMax = mCycles + mMaxSeq; mExpectedPackets = extendedMax - mBaseSeq + 1; - - Debug( 5, "Expected packets = %d", mExpectedPackets ); - // The number of packets lost is defined to be the number of packets // expected less the number of packets actually received: mLostPackets = mExpectedPackets - mReceivedPackets; - Debug( 5, "Lost packets = %d", mLostPackets ); - uint32_t expectedInterval = mExpectedPackets - mExpectedPrior; - Debug( 5, "Expected interval = %d", expectedInterval ); mExpectedPrior = mExpectedPackets; uint32_t receivedInterval = mReceivedPackets - mReceivedPrior; - Debug( 5, "Received interval = %d", receivedInterval ); mReceivedPrior = mReceivedPackets; uint32_t lostInterval = expectedInterval - receivedInterval; - Debug( 5, "Lost interval = %d", lostInterval ); if ( expectedInterval == 0 || lostInterval <= 0 ) mLostFraction = 0; else mLostFraction = (lostInterval << 8) / expectedInterval; - Debug( 5, "Lost fraction = %d", mLostFraction ); + + 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); } -bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen ) { +bool RtpSource::handlePacket(const unsigned char *packet, size_t packetLen) { const RtpDataHeader *rtpHeader; rtpHeader = (RtpDataHeader *)packet; int rtpHeaderSize = 12 + rtpHeader->cc * 4; @@ -248,15 +265,15 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen ) { // that there is no marker bit by changing the number of bits in the payload type field. bool thisM = rtpHeader->m || h264FragmentEnd; - if ( updateSeq( ntohs(rtpHeader->seqN) ) ) { - Hexdump( 4, packet+rtpHeaderSize, 16 ); + if ( updateSeq(ntohs(rtpHeader->seqN)) ) { + Hexdump(4, packet+rtpHeaderSize, 16); if ( mFrameGood ) { int extraHeader = 0; if ( mCodecId == AV_CODEC_ID_H264 ) { int nalType = (packet[rtpHeaderSize] & 0x1f); - Debug( 3, "Have H264 frame: nal type is %d", nalType ); + Debug(3, "Have H264 frame: nal type is %d", nalType); switch (nalType) { case 24: // STAP-A @@ -280,46 +297,52 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen ) { extraHeader = 2; break; - default: - Debug(3, "Unhandled nalType %d", nalType ); + default: + Debug(3, "Unhandled nalType %d", nalType); } // Append NAL frame start code if ( !mFrame.size() ) - mFrame.append( "\x0\x0\x1", 3 ); + mFrame.append("\x0\x0\x1", 3); } // end if H264 - mFrame.append( packet+rtpHeaderSize+extraHeader, packetLen-rtpHeaderSize-extraHeader ); + mFrame.append(packet+rtpHeaderSize+extraHeader, + packetLen-rtpHeaderSize-extraHeader); } else { - Debug( 3, "NOT H264 frame: type is %d", mCodecId ); + Debug(3, "NOT H264 frame: type is %d", mCodecId); } - Hexdump( 4, mFrame.head(), 16 ); + Hexdump(4, mFrame.head(), 16); if ( thisM ) { if ( mFrameGood ) { - Debug( 3, "Got new frame %d, %d bytes", mFrameCount, mFrame.size() ); + 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() ); + Warning("Discarding incomplete frame %d, %d bytes", mFrameCount, mFrame.size()); } mFrame.clear(); } } else { if ( mFrame.size() ) { - Warning( "Discarding partial frame %d, %d bytes", mFrameCount, mFrame.size() ); + Warning("Discarding partial frame %d, %d bytes", mFrameCount, mFrame.size()); } else { - Warning( "Discarding frame %d", mFrameCount ); + Warning("Discarding frame %d", mFrameCount); } mFrameGood = false; mFrame.clear(); @@ -335,19 +358,22 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen ) { return true; } -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; +bool RtpSource::getFrame(Buffer &buffer) { + { + 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 b53fb975b..71be9af2c 100644 --- a/src/zm_rtp_source.h +++ b/src/zm_rtp_source.h @@ -21,14 +21,14 @@ #define ZM_RTP_SOURCE_H #include "zm_buffer.h" +#include "zm_config.h" +#include "zm_define.h" #include "zm_ffmpeg.h" -#include "zm_thread.h" - -#include -#include +#include "zm_time.h" +#include +#include #include - -#if HAVE_LIBAVCODEC +#include struct RtpDataHeader; @@ -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,22 @@ private: int mFrameCount; bool mFrameGood; bool prevM; - ThreadData mFrameReady; - ThreadData mFrameProcessed; + + bool mFrameReady; + std::condition_variable mFrameReadyCv; + std::mutex mFrameReadyMutex; + + bool mFrameProcessed; + std::condition_variable mFrameProcessedCv; + std::mutex mFrameProcessedMutex; + bool mTerminate; private: - void init( uint16_t seq ); + 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 +191,4 @@ public: } }; -#endif // HAVE_LIBAVCODEC - #endif // ZM_RTP_SOURCE_H diff --git a/src/zm_rtsp.cpp b/src/zm_rtsp.cpp index 5e2858e4f..1daecf18b 100644 --- a/src/zm_rtsp.cpp +++ b/src/zm_rtsp.cpp @@ -17,36 +17,30 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" - -#if HAVE_LIBAVFORMAT - #include "zm_rtsp.h" +#include "zm_config.h" #include "zm_rtp_data.h" #include "zm_rtp_ctrl.h" #include "zm_db.h" -#include -#include -#include -#include +#include int RtspThread::smMinDataPort = 0; int RtspThread::smMaxDataPort = 0; -RtspThread::PortSet RtspThread::smAssignedPorts; +RtspThread::PortSet RtspThread::smAssignedPorts; -bool RtspThread::sendCommand( std::string message ) { +bool RtspThread::sendCommand(std::string message) { if ( mNeedAuth ) { - StringVector parts = split( message, " " ); - if (parts.size() > 1) + StringVector parts = Split(message, " "); + if ( parts.size() > 1 ) message += mAuthenticator->getAuthHeader(parts[0], parts[1]); } - message += stringtf( "User-Agent: ZoneMinder/%s\r\n", ZM_VERSION ); - message += stringtf( "CSeq: %d\r\n\r\n", ++mSeq ); - Debug( 2, "Sending RTSP message: %s", message.c_str() ); + message += stringtf("User-Agent: ZoneMinder/%s\r\n", ZM_VERSION); + 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)); @@ -61,8 +55,8 @@ bool RtspThread::sendCommand( std::string message ) { return true; } -bool RtspThread::recvResponse( std::string &response ) { - if ( mRtspSocket.recv( response ) < 0 ) +bool RtspThread::recvResponse(std::string &response) { + if ( mRtspSocket.recv(response) < 0 ) Error("Recv failed; %s", strerror(errno)); Debug(2, "Received RTSP response: %s (%zd bytes)", response.c_str(), response.size()); float respVer = 0; @@ -74,12 +68,12 @@ bool RtspThread::recvResponse( std::string &response ) { } else { Error("Response parse failure, %zd bytes follow", response.size()); if ( response.size() ) - Hexdump( Logger::ERROR, response.data(), min(response.size(),16) ); + Hexdump(Logger::ERROR, response.data(), std::min(int(response.size()), 16)); } return false; } if ( respCode == 401 ) { - Debug( 2, "Got 401 access denied response code, check WWW-Authenticate header and retry"); + Debug(2, "Got 401 access denied response code, check WWW-Authenticate header and retry"); mAuthenticator->checkAuthResponse(response); mNeedAuth = true; return false; @@ -88,24 +82,16 @@ bool RtspThread::recvResponse( std::string &response ) { return false; } return true; -} // end RtspThread::recResponse +} // end RtspThread::recvResponse 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 = 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 ); + MYSQL_RES *result = zmDbFetch(sql); + + int nMonitors = mysql_num_rows(result); int position = 0; if ( nMonitors ) { for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { @@ -124,7 +110,7 @@ int RtspThread::requestPorts() { int portRange = int(((config.max_rtp_port-config.min_rtp_port)+1)/nMonitors); smMinDataPort = config.min_rtp_port + (position * portRange); smMaxDataPort = smMinDataPort + portRange - 1; - Debug( 2, "Assigned RTP port range is %d-%d", smMinDataPort, smMaxDataPort ); + Debug(2, "Assigned RTP port range is %d-%d", smMinDataPort, smMaxDataPort); } for ( int i = smMinDataPort; i <= smMaxDataPort; i++ ) { PortSet::const_iterator iter = smAssignedPorts.find(i); @@ -137,7 +123,7 @@ int RtspThread::requestPorts() { return -1; } -void RtspThread::releasePorts( int port ) { +void RtspThread::releasePorts(int port) { if ( port > 0 ) smAssignedPorts.erase(port); } @@ -151,21 +137,21 @@ RtspThread::RtspThread( const std::string &path, const std::string &auth, bool rtsp_describe) : - mId( id ), - mMethod( method ), - mProtocol( protocol ), - mHost( host ), - mPort( port ), - mPath( path ), - mRtspDescribe( rtsp_describe ), - mSessDesc( 0 ), - mFormatContext( 0 ), - mSeq( 0 ), - mSession( 0 ), - mSsrc( 0 ), - mDist( UNDEFINED ), - mRtpTime( 0 ), - mStop( false ) + mId(id), + mMethod(method), + mProtocol(protocol), + mHost(host), + mPort(port), + mPath(path), + mRtspDescribe(rtsp_describe), + mSessDesc(0), + mFormatContext(0), + mSeq(0), + mSession(0), + mSsrc(0), + mDist(UNDEFINED), + mRtpTime(0), + mTerminate(false) { mUrl = mProtocol+"://"+mHost+":"+mPort; if ( !mPath.empty() ) { @@ -177,43 +163,47 @@ RtspThread::RtspThread( mSsrc = rand(); - Debug(2, "RTSP Local SSRC is %x", mSsrc); + Debug(2, "RTSP Local SSRC is %x, url is %s", mSsrc, mUrl.c_str()); if ( mMethod == RTP_RTSP_HTTP ) mHttpSession = stringtf("%d", rand()); mNeedAuth = false; - StringVector parts = split(auth,":"); - if (parts.size() > 1) + 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 = NULL; + avformat_free_context(mFormatContext); + mFormatContext = nullptr; } if ( mSessDesc ) { delete mSessDesc; - mSessDesc = NULL; + mSessDesc = nullptr; } delete mAuthenticator; + mAuthenticator = nullptr; } -int RtspThread::run() { +void RtspThread::Run() { std::string message; std::string response; - response.reserve( ZM_NETWORK_BUFSIZ ); + response.reserve(ZM_NETWORK_BUFSIZ); - if ( !mRtspSocket.connect( mHost.c_str(), mPort.c_str() ) ) - Fatal( "Unable to connect RTSP socket" ); + if ( !mRtspSocket.connect(mHost.c_str(), mPort.c_str()) ) + Fatal("Unable to connect RTSP socket"); //Select select( 0.25 ); //select.addReader( &mRtspSocket ); //while ( select.wait() ) @@ -221,7 +211,6 @@ int RtspThread::run() { //mRtspSocket.recv( response ); //Debug( 4, "Drained %d bytes from RTSP socket", response.size() ); //} - bool authTried = false; if ( mMethod == RTP_RTSP_HTTP ) { @@ -245,30 +234,29 @@ int RtspThread::run() { message += mAuthenticator->getAuthHeader("GET", mPath); authTried = true; } - message += "Accept: application/x-rtsp-tunnelled\r\n"; - message += "\r\n"; - Debug( 2, "Sending HTTP message: %s", message.c_str() ); - if ( mRtspSocket.send( message.c_str(), message.size() ) != (int)message.length() ) { + message += "Accept: application/x-rtsp-tunnelled\r\n\r\n"; + 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 ) { + 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()); float respVer = 0; respCode = -1; - if ( sscanf( response.c_str(), "HTTP/%f %3d %[^\r\n]\r\n", &respVer, &respCode, respText ) != 3 ) { + if ( sscanf(response.c_str(), "HTTP/%f %3d %[^\r\n]\r\n", &respVer, &respCode, respText) != 3 ) { if ( isalnum(response[0]) ) { - Error( "Response parse failure in '%s'", response.c_str() ); + Error("Response parse failure in '%s'", response.c_str()); } else { - Error( "Response parse failure, %zd bytes follow", response.size() ); + Error("Response parse failure, %zd bytes follow", response.size()); if ( response.size() ) - Hexdump( Logger::ERROR, response.data(), min(response.size(),16) ); + Hexdump(Logger::ERROR, response.data(), std::min(int(response.size()), 16)); } - return -1; + return; } // If Server requests authentication, check WWW-Authenticate header and fill required fields // for requested authentication method @@ -286,7 +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"; @@ -296,36 +284,36 @@ int RtspThread::run() { message += "Content-Length: 32767\r\n"; message += "Content-Type: application/x-rtsp-tunnelled\r\n"; message += "\r\n"; - 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 ); + 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; } - } + } // end if ( mMethod == RTP_RTSP_HTTP ) std::string localHost = ""; int localPorts[2] = { 0, 0 }; // Request supported RTSP commands by the server message = "OPTIONS "+mUrl+" RTSP/1.0\r\n"; - if ( !sendCommand( message ) ) - return -1; + if ( !sendCommand(message) ) + 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" ); + 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,11 +326,12 @@ int RtspThread::run() { message = "DESCRIBE "+mUrl+" RTSP/1.0\r\n"; bool res; do { - if (mNeedAuth) + if ( mNeedAuth ) authTried = true; sendCommand(message); - // FIXME WHy sleep 1? - sleep(1); + // FIXME Why sleep 1? + std::this_thread::sleep_for(Microseconds(10)); + res = recvResponse(response); if ( !res && respCode==401 ) mNeedAuth = true; @@ -351,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; } @@ -376,9 +365,9 @@ int RtspThread::run() { try { mSessDesc = new SessionDescriptor( mUrl, sdp ); mFormatContext = mSessDesc->generateFormatContext(); - } catch( const Exception &e ) { - Error( e.getMessage().c_str() ); - return -1; + } catch ( const Exception &e ) { + Error("%s", e.getMessage().c_str()); + return; } #if 0 @@ -387,7 +376,7 @@ int RtspThread::run() { if ( !mAuth.empty() ) authUrl.insert( authUrl.find( "://" )+3, mAuth+"@" ); - if ( av_open_input_file( &mFormatContext, authUrl.c_str(), NULL, 0, NULL ) != 0 ) + if ( av_open_input_file( &mFormatContext, authUrl.c_str(), nullptr, 0, nullptr ) != 0 ) { Error( "Unable to open input '%s'", authUrl.c_str() ); return( -1 ); @@ -402,123 +391,114 @@ int RtspThread::run() { if ( mFormatContext->nb_streams >= 1 ) { for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) { - SessionDescriptor::MediaDescriptor *mediaDesc = mSessDesc->getStream( i ); -#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) - if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO ) -#else - if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) -#endif - { + SessionDescriptor::MediaDescriptor *mediaDesc = mSessDesc->getStream(i); + 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() != '/') { + if ( *trackUrl.rbegin() != '/' ) { trackUrl += "/" + controlUrl; } else { trackUrl += controlUrl; } } rtpClock = mediaDesc->getClock(); - codecId = mFormatContext->streams[i]->codec->codec_id; - // Hackery pokery - //rtpClock = mFormatContext->streams[i]->codec->sample_rate; + codecId = mFormatContext->streams[i]->codecpar->codec_id; break; - } - } - } + } // end if is video + } // end foreach stream + } // end if have stream - switch( mMethod ) { + switch ( mMethod ) { case RTP_UNICAST : - { localPorts[0] = requestPorts(); localPorts[1] = localPorts[0]+1; - message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP;unicast;client_port="+stringtf( "%d", localPorts[0] )+"-"+stringtf( "%d", localPorts[1] )+"\r\n"; + message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP;unicast;client_port=" + +stringtf("%d", localPorts[0] )+"-"+stringtf( "%d", localPorts[1])+"\r\n"; break; - } case RTP_MULTICAST : - { message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP;multicast\r\n"; break; - } case RTP_RTSP : case RTP_RTSP_HTTP : - { message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP/TCP;unicast\r\n"; break; - } default: - { - Panic( "Got unexpected method %d", mMethod ); + Panic("Got unexpected method %d", mMethod); break; - } } - if ( !sendCommand( message ) ) - return( -1 ); - if ( !recvResponse( response ) ) - return( -1 ); + if ( !sendCommand(message) ) + return; + if ( !recvResponse(response) ) + return; - lines = split( response, "\r\n" ); + lines = Split(response, "\r\n"); std::string session; - int timeout = 0; + 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 ); + 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 ){ + 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 ); + sscanf(lines[i].c_str(), "Transport: %s", transport); } if ( session.empty() ) - Fatal( "Unable to get session identifier from response '%s'", response.c_str() ); + Fatal("Unable to get session identifier from response '%s'", response.c_str()); - Debug( 2, "Got RTSP session %s, timeout %d secs", session.c_str(), timeout ); + Debug(2, "Got RTSP session %s, timeout %" 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() ); + Fatal("Unable to get transport details from response '%s'", response.c_str()); - Debug( 2, "Got RTSP transport %s", transport ); + Debug(2, "Got RTSP transport %s", transport); std::string method = ""; int remotePorts[2] = { 0, 0 }; int remoteChannels[2] = { 0, 0 }; std::string distribution = ""; unsigned long ssrc = 0; - StringVector parts = split( transport, ";" ); + StringVector parts = Split(transport, ";"); for ( size_t i = 0; i < parts.size(); i++ ) { if ( parts[i] == "unicast" || parts[i] == "multicast" ) distribution = parts[i]; - else if ( startsWith( parts[i], "server_port=" ) ) { + else if (StartsWith(parts[i], "server_port=") ) { method = "RTP/UNICAST"; - StringVector subparts = split( parts[i], "=" ); - StringVector ports = split( subparts[1], "-" ); - remotePorts[0] = strtol( ports[0].c_str(), NULL, 10 ); - remotePorts[1] = strtol( ports[1].c_str(), NULL, 10 ); - } else if ( startsWith( parts[i], "interleaved=" ) ) { + 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=") ) { method = "RTP/RTSP"; - StringVector subparts = split( parts[i], "=" ); - StringVector channels = split( subparts[1], "-" ); - remoteChannels[0] = strtol( channels[0].c_str(), NULL, 10 ); - remoteChannels[1] = strtol( channels[1].c_str(), NULL, 10 ); - } else if ( startsWith( parts[i], "port=" ) ) { + 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=") ) { method = "RTP/MULTICAST"; - StringVector subparts = split( parts[i], "=" ); - StringVector ports = split( subparts[1], "-" ); - localPorts[0] = strtol( ports[0].c_str(), NULL, 10 ); - localPorts[1] = strtol( ports[1].c_str(), NULL, 10 ); - } else if ( startsWith( parts[i], "destination=" ) ) { - StringVector subparts = split( parts[i], "=" ); + 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], "="); localHost = subparts[1]; - } else if ( startsWith( parts[i], "ssrc=" ) ) { - StringVector subparts = split( parts[i], "=" ); - ssrc = strtoll( subparts[1].c_str(), NULL, 16 ); + } else if (StartsWith(parts[i], "ssrc=") ) { + StringVector subparts = Split(parts[i], "="); + ssrc = strtoll( subparts[1].c_str(), nullptr, 16 ); } } @@ -531,23 +511,29 @@ int RtspThread::run() { Debug( 2, "RTSP Remote Channels are %d/%d", remoteChannels[0], remoteChannels[1] ); message = "PLAY "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\nRange: npt=0.000-\r\n"; - if ( !sendCommand( message ) ) - return( -1 ); - if ( !recvResponse( response ) ) - return( -1 ); + if ( !sendCommand(message) ) + return; + if ( !recvResponse(response) ) + return; - lines = split( response, "\r\n" ); + lines = Split(response, "\r\n"); std::string rtpInfo; for ( size_t i = 0; i < lines.size(); i++ ) { - if ( ( lines[i].size() > 9 ) && ( lines[i].substr( 0, 9 ) == "RTP-Info:" ) ) - rtpInfo = trimSpaces( lines[i].substr( 9 ) ); - // Check for a timeout again. Some rtsp devices don't send a timeout until after the PLAY command is sent - if ( ( lines[i].size() > 8 ) && ( lines[i].substr( 0, 8 ) == "Session:" ) && ( timeout == 0 ) ) { - StringVector sessionLine = split( lines[i].substr(9), ";" ); - if ( 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() > 9 ) && ( lines[i].substr(0, 9) == "RTP-Info:" ) ) + rtpInfo = TrimSpaces(lines[i].substr(9)); + // Check for a timeout again. Some rtsp devices don't send a timeout until after the PLAY command is sent + if ((lines[i].size() > 8) && (lines[i].substr(0, 8) == "Session:") && (timeout == 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())); + } } } @@ -559,19 +545,19 @@ 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], "=" ); - seq = strtol( subparts[1].c_str(), NULL, 10 ); - } else if ( startsWith( parts[j], "rtptime=" ) ) { - StringVector subparts = split( parts[j], "=" ); - rtpTime = strtol( subparts[1].c_str(), NULL, 10 ); + 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], "="); + rtpTime = strtol( subparts[1].c_str(), nullptr, 10 ); } } break; @@ -582,8 +568,8 @@ int RtspThread::run() { Debug( 2, "RTSP Seq is %d", seq ); Debug( 2, "RTSP Rtptime is %ld", rtpTime ); - time_t lastKeepalive = time(NULL); - 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 ) { @@ -594,19 +580,22 @@ int RtspThread::run() { RtpDataThread rtpDataThread( *this, *source ); RtpCtrlThread rtpCtrlThread( *this, *source ); - rtpDataThread.start(); - rtpCtrlThread.start(); - - while( !mStop ) { - now = time(NULL); + 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"; @@ -618,19 +607,16 @@ Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepali 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(); @@ -647,14 +633,14 @@ Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepali RtpDataThread rtpDataThread( *this, *source ); RtpCtrlThread rtpCtrlThread( *this, *source ); - 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 ) { - 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; @@ -678,22 +664,21 @@ Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepali break; } if ( channel == remoteChannels[0] ) { - Debug( 4, "Got %d bytes on data channel %d, packet length is %d", buffer.size(), channel, len ); - Hexdump( 4, (char *)buffer, 16 ); - rtpDataThread.recvPacket( buffer+4, len ); - Debug( 4, "Received" ); + Debug(4, "Got %d bytes on data channel %d, packet length is %d", buffer.size(), channel, len); + Hexdump(4, (char *)buffer, 16); + rtpDataThread.recvPacket(buffer+4, len); } else if ( channel == remoteChannels[1] ) { // len = ntohs( *((unsigned short *)(buffer+2)) ); // Debug( 4, "Got %d bytes on control channel %d", nBytes, channel ); - Debug( 4, "Got %d bytes on control channel %d, packet length is %d", buffer.size(), channel, len ); - Hexdump( 4, (char *)buffer, 16 ); - rtpCtrlThread.recvPackets( buffer+4, len ); + Debug(4, "Got %d bytes on control channel %d, packet length is %d", buffer.size(), channel, len); + Hexdump(4, (char *)buffer, 16); + rtpCtrlThread.recvPackets(buffer+4, len); } else { - Error( "Unexpected channel selector %d in RTSP interleaved data", buffer[1] ); + Error("Unexpected channel selector %d in RTSP interleaved data", buffer[1]); buffer.clear(); break; } - buffer.consume( len+4 ); + buffer.consume(len+4); nBytes -= len+4; } else { if ( keepaliveResponse.compare( 0, keepaliveResponse.size(), (char *)buffer, keepaliveResponse.size() ) == 0 ) { @@ -721,16 +706,23 @@ Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepali } // 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(NULL); + 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"; @@ -742,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(); @@ -756,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(NULL)-lastKeepalive) > (timeout-5)) ) { - if ( !sendCommand( message ) ) - return -1; - lastKeepalive = time(NULL); + 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"; @@ -777,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(); @@ -798,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 14ef02010..96906e209 100644 --- a/src/zm_rtsp.h +++ b/src/zm_rtsp.h @@ -20,18 +20,16 @@ #ifndef ZM_RTSP_H #define ZM_RTSP_H -#include "zm.h" -#include "zm_ffmpeg.h" #include "zm_comms.h" -#include "zm_thread.h" #include "zm_rtp_source.h" #include "zm_rtsp_auth.h" #include "zm_sdp.h" - -#include +#include #include +#include +#include -class RtspThread : public Thread { +class RtspThread { public: typedef enum { RTP_UNICAST, RTP_MULTICAST, RTP_RTSP, RTP_RTSP_HTTP } RtspMethod; typedef enum { UNDEFINED, UNICAST, MULTICAST } RtspDist; @@ -70,8 +68,8 @@ private: std::string mHttpSession; ///< Only for RTSP over HTTP sessions - TcpInetClient mRtspSocket; - TcpInetClient mRtspSocket2; + zm::TcpInetClient mRtspSocket; + zm::TcpInetClient mRtspSocket2; SourceMap mSources; @@ -88,12 +86,14 @@ private: unsigned long mRtpTime; - bool mStop; + std::thread mThread; + std::atomic mTerminate; private: bool sendCommand( std::string message ); bool recvResponse( std::string &response ); - void checkAuthResponse(std::string &response); + void checkAuthResponse(std::string &response); + void Run(); public: RtspThread( int id, RtspMethod method, const std::string &protocol, const std::string &host, const std::string &port, const std::string &path, const std::string &auth, bool rtsp_describe ); @@ -128,15 +128,10 @@ public: return( false ); return( iter->second->getFrame( frame ) ); } - int run(); - void stop() - { - mStop = true; - } - bool stopped() const - { - return( mStop ); - } + + void Stop() { mTerminate = true; } + bool IsStopped() const { return mTerminate; } + int getAddressFamily () { return mRtspSocket.getDomain(); diff --git a/src/zm_rtsp_auth.cpp b/src/zm_rtsp_auth.cpp index 81aa13c27..cf2be15f4 100644 --- a/src/zm_rtsp_auth.cpp +++ b/src/zm_rtsp_auth.cpp @@ -14,35 +14,24 @@ // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// +// -#include "zm.h" -#include "zm_utils.h" #include "zm_rtsp_auth.h" -#include -#include -#include +#include "zm_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(); @@ -56,57 +45,56 @@ void Authenticator::reset() { fAuthMethod = AUTH_UNDEFINED; } -void Authenticator::authHandleHeader(std::string headerData) -{ +void Authenticator::authHandleHeader(std::string headerData) { const char* basic_match = "Basic "; const char* digest_match = "Digest "; size_t digest_match_len = strlen(digest_match); // Check if basic auth - if ( strncasecmp(headerData.c_str(),basic_match,strlen(basic_match)) == 0 ) { + if ( strncasecmp(headerData.c_str(), basic_match, strlen(basic_match)) == 0 ) { fAuthMethod = AUTH_BASIC; Debug(2, "Set authMethod to Basic"); } // Check if digest auth - else if (strncasecmp( headerData.c_str(),digest_match,digest_match_len ) == 0) { + 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), ","); + Debug(2, "Set authMethod to Digest"); + 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; } } Debug(2, "Auth data completed. User: %s, realm: %s, nonce: %s, qop: %s", username().c_str(), fRealm.c_str(), fNonce.c_str(), fQop.c_str()); } -} +} // 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() ); - } else if (fAuthMethod == AUTH_DIGEST) { + if ( fAuthMethod == AUTH_BASIC ) { + result += "Basic " + Base64Encode(username() + ":" + password()); + } else if ( fAuthMethod == AUTH_DIGEST ) { result += std::string("Digest ") + "username=\"" + quote(username()) + "\", realm=\"" + quote(realm()) + "\", " + "nonce=\"" + quote(nonce()) + "\", uri=\"" + quote(uri) + "\""; - if ( ! fQop.empty() ) { + if ( !fQop.empty() ) { result += ", qop=" + fQop; result += ", nc=" + stringtf("%08x",nc); result += ", cnonce=\"" + fCnonce + "\""; @@ -130,94 +118,62 @@ 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(), 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(), 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(), 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); for ( size_t i = 0; i < lines.size(); i++ ) { // stop at end of headers - if (lines[i].length()==0) + if ( lines[i].length() == 0 ) break; - if (strncasecmp(lines[i].c_str(),authenticate_match,authenticate_match_len) == 0) { + if ( strncasecmp(lines[i].c_str(), authenticate_match, authenticate_match_len) == 0 ) { authLine = lines[i]; - Debug( 2, "Found auth line at %d", i); - break; + 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)) ); + if ( !authLine.empty() ) { + Debug(2, "Analyze auth line %s", authLine.c_str()); + 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()); + Debug(2, "Didn't find auth line in %s", authLine.c_str()); } -} +} // end void Authenticator::checkAuthResponse(std::string &response) } // namespace zm diff --git a/src/zm_rtsp_auth.h b/src/zm_rtsp_auth.h index 34056eee6..52d186d26 100644 --- a/src/zm_rtsp_auth.h +++ b/src/zm_rtsp_auth.h @@ -19,25 +19,15 @@ #ifndef ZM_RTSP_AUTH_H #define ZM_RTSP_AUTH_H -#if HAVE_GNUTLS_OPENSSL_H -#include -#endif -#if HAVE_GNUTLS_GNUTLS_H -#include -#endif +#include "zm_config.h" +#include -#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..3da273e00 --- /dev/null +++ b/src/zm_rtsp_server.cpp @@ -0,0 +1,354 @@ +// +// 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 if (std::string::npos != audioFifoPath.find("pcm_alaw")) { + Debug(1, "Adding G711A source at %dHz %d channels", + monitor->GetAudioFrequency(), monitor->GetAudioChannels()); + session->AddSource(xop::channel_1, xop::G711ASource::CreateNew()); + 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.cpp b/src/zm_rtsp_server_adts_source.cpp new file mode 100644 index 000000000..1c0c1a5a4 --- /dev/null +++ b/src/zm_rtsp_server_adts_source.cpp @@ -0,0 +1,48 @@ +/* --------------------------------------------------------------------------- +** +** ADTS_DeviceSource.cpp +** +** ADTS Live555 source +** +** -------------------------------------------------------------------------*/ + +#include "zm_rtsp_server_adts_source.h" + +#include "zm_config.h" +#include + +#if HAVE_RTSP_SERVER +// live555 +#include + +static unsigned const samplingFrequencyTable[16] = { + 96000, 88200, 64000, 48000, + 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, + 7350, 0, 0, 0 +}; +// --------------------------------- +// ADTS ZoneMinder FramedSource +// --------------------------------- +// +ADTS_ZoneMinderDeviceSource::ADTS_ZoneMinderDeviceSource( + UsageEnvironment& env, + std::shared_ptr monitor, + AVStream *stream, + unsigned int queueSize + ) + : + ZoneMinderDeviceSource(env, std::move(monitor), stream, queueSize), + samplingFrequencyIndex(0), + channels(stream->codecpar->channels) +{ + std::ostringstream os; + os << + "profile-level-id=1;" + "mode=AAC-hbr;sizelength=13;indexlength=3;" + "indexdeltalength=3" + //<< extradata2psets(nullptr, m_stream) + << "\r\n"; + m_auxLine.assign(os.str()); +} +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_adts_source.h b/src/zm_rtsp_server_adts_source.h new file mode 100644 index 000000000..bae93b8bd --- /dev/null +++ b/src/zm_rtsp_server_adts_source.h @@ -0,0 +1,61 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** ADTS_ZoneMinderDeviceSource.h +** +** ADTS ZoneMinder live555 source +** +** -------------------------------------------------------------------------*/ + +#ifndef ZM_RTSP_SERVER_ADTS_SOURCE_H +#define ZM_RTSP_SERVER_ADTS_SOURCE_H + +#include "zm_config.h" +#include "zm_rtsp_server_device_source.h" + +#if HAVE_RTSP_SERVER +// --------------------------------- +// ADTS(AAC) ZoneMinder FramedSource +// --------------------------------- + +class ADTS_ZoneMinderDeviceSource : public ZoneMinderDeviceSource { + public: + static ADTS_ZoneMinderDeviceSource* createNew( + UsageEnvironment& env, + std::shared_ptr monitor, + AVStream * stream, + unsigned int queueSize + ) { + return new ADTS_ZoneMinderDeviceSource(env, std::move(monitor), stream, queueSize); + }; + protected: + ADTS_ZoneMinderDeviceSource( + UsageEnvironment& env, + std::shared_ptr monitor, + AVStream *stream, + unsigned int queueSize + ); + + virtual ~ADTS_ZoneMinderDeviceSource() {} + + /* + virtual unsigned char* extractFrame(unsigned char* frame, size_t& size, size_t& outsize); + virtual unsigned char* findMarker(unsigned char *frame, size_t size, size_t &length); + */ + public: + int samplingFrequency() { return m_stream->codecpar->sample_rate; }; + const char *configStr() { return config.c_str(); }; + int numChannels() { + return channels; + } + + protected: + std::string config; + int samplingFrequencyIndex; + int channels; +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_ADTS_SOURCE_H diff --git a/src/zm_rtsp_server_authenticator.h b/src/zm_rtsp_server_authenticator.h new file mode 100644 index 000000000..5793feb32 --- /dev/null +++ b/src/zm_rtsp_server_authenticator.h @@ -0,0 +1,95 @@ +#ifndef ZM_RTSP_SERVER_AUTHENTICATOR_H +#define ZM_RTSP_SERVER_AUTHENTICATOR_H + +#include "zm_config.h" +#include "zm_user.h" + +#include +#include + +#include "xop/Authenticator.h" +#include "xop/RtspMessage.h" + +#if HAVE_RTSP_SERVER + +class ZM_RtspServer_Authenticator : public xop::Authenticator { + public: + ZM_RtspServer_Authenticator() {}; + ~ZM_RtspServer_Authenticator() {}; + + bool Authenticate(std::shared_ptr request, std::string &nonce) { + + if (!config.opt_use_auth) { + Debug(1, "Not doing auth"); + return true; + } + std::string url = request->GetRtspUrl(); + Debug(1, "Doing auth %s", url.c_str()); + + User *user = nullptr; + + size_t found = url.find("?"); + if (found == std::string::npos) return false; + + std::string queryString = url.substr(found+1, std::string::npos); + +#if 0 + found = suffix_string.find("/"); + if ( found != std::string::npos ) + suffix_string = suffix_string.substr(0, found); +#endif + + Debug(1, "suffix %s", queryString.c_str()); + std::istringstream requestStream(queryString); + QueryString query(requestStream); + + if (query.has("jwt_token")) { + const QueryParameter *jwt_token = query.get("jwt_token"); + user = zmLoadTokenUser(jwt_token->firstValue(), false); + } else if (query.has("token")) { + const QueryParameter *jwt_token = query.get("token"); + user = zmLoadTokenUser(jwt_token->firstValue(), false); + } else if (strcmp(config.auth_relay, "none") == 0) { + if (query.has("username")) { + std::string username = query.get("username")->firstValue(); + if (checkUser(username.c_str())) { + user = zmLoadUser(username.c_str()); + } else { + Debug(1, "Bad username %s", username.c_str()); + } + } + } else { + if (query.has("auth")) { + std::string auth_hash = query.get("auth")->firstValue(); + if ( !auth_hash.empty() ) + user = zmLoadAuthUser(auth_hash.c_str(), config.auth_hash_ips); + } + Debug(1, "Query has username ? %d", query.has("username")); + if ((!user) and query.has("username") and query.has("password")) { + std::string username = query.get("username")->firstValue(); + std::string password = query.get("password")->firstValue(); + Debug(1, "username %s password %s", username.c_str(), password.c_str()); + user = zmLoadUser(username.c_str(), password.c_str()); + } + } // end if query string + + if (user) { + Debug(1, "Authenticated"); + delete user; + return true; + } + return false; + } + + size_t GetFailedResponse( + std::shared_ptr request, + std::shared_ptr buf, + size_t size) { + return request->BuildUnauthorizedRes(buf.get(), size); + } + +}; // end class ZM_RtspServer_Authenticator + +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_AUTHENTICATOR_H diff --git a/src/zm_rtsp_server_device_source.cpp b/src/zm_rtsp_server_device_source.cpp new file mode 100644 index 000000000..ba648b2f4 --- /dev/null +++ b/src/zm_rtsp_server_device_source.cpp @@ -0,0 +1,217 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** +** ZoneMinder Live555 source +** +** -------------------------------------------------------------------------*/ + +#include "zm_rtsp_server_device_source.h" + +#include "zm_config.h" +#include "zm_logger.h" +#include "zm_rtsp_server_frame.h" +#include "zm_signal.h" + +#if HAVE_RTSP_SERVER +ZoneMinderDeviceSource::ZoneMinderDeviceSource( + UsageEnvironment& env, + std::shared_ptr monitor, + AVStream *stream, + unsigned int queueSize + ) : + FramedSource(env), + m_eventTriggerId(envir().taskScheduler().createEventTrigger(ZoneMinderDeviceSource::deliverFrameStub)), + m_stream(stream), + m_monitor(std::move(monitor)), + m_packetqueue(nullptr), + m_packetqueue_it(nullptr), + m_queueSize(queueSize) +{ + memset(&m_thid, 0, sizeof(m_thid)); + memset(&m_mutex, 0, sizeof(m_mutex)); + if ( m_monitor ) { + m_packetqueue = m_monitor->GetPacketQueue(); + if ( !m_packetqueue ) { + Fatal("No packetqueue"); + } + pthread_mutex_init(&m_mutex, nullptr); + pthread_create(&m_thid, nullptr, threadStub, this); + } else { + Error("No monitor in ZoneMinderDeviceSource"); + } +} + +ZoneMinderDeviceSource::~ZoneMinderDeviceSource() { + stop = 1; + envir().taskScheduler().deleteEventTrigger(m_eventTriggerId); + pthread_join(m_thid, nullptr); + while ( m_captureQueue.size() ) { + NAL_Frame * f = m_captureQueue.front(); + m_captureQueue.pop_front(); + delete f; + } + + pthread_mutex_destroy(&m_mutex); +} + +// thread mainloop +void* ZoneMinderDeviceSource::thread() { + stop = 0; + + while ( !stop ) { + getNextFrame(); + } + return nullptr; +} + +// getting FrameSource callback +void ZoneMinderDeviceSource::doGetNextFrame() { + deliverFrame(); +} + +// stopping FrameSource callback +void ZoneMinderDeviceSource::doStopGettingFrames() { + stop = 1; + Debug(1, "ZoneMinderDeviceSource::doStopGettingFrames"); + FramedSource::doStopGettingFrames(); +} + +// deliver frame to the sink +void ZoneMinderDeviceSource::deliverFrame() { + if ( !isCurrentlyAwaitingData() ) { + Debug(4, "not awaiting data"); + return; + } + + pthread_mutex_lock(&m_mutex); + if ( m_captureQueue.empty() ) { + Debug(4, "Queue is empty"); + pthread_mutex_unlock(&m_mutex); + return; + } + + NAL_Frame *frame = m_captureQueue.front(); + m_captureQueue.pop_front(); + pthread_mutex_unlock(&m_mutex); + + fDurationInMicroseconds = 0; + fFrameSize = 0; + + unsigned int nal_size = frame->size(); + + if ( nal_size > fMaxSize ) { + fFrameSize = fMaxSize; + fNumTruncatedBytes = nal_size - fMaxSize; + } else { + fFrameSize = nal_size; + } + Debug(2, "deliverFrame stream: %d timestamp: %ld.%06ld size: %d queuesize: %d", + m_stream->index, + frame->m_timestamp.tv_sec, frame->m_timestamp.tv_usec, + fFrameSize, + m_captureQueue.size() + ); + + fPresentationTime = frame->m_timestamp; + memcpy(fTo, frame->buffer(), fFrameSize); + + if ( fFrameSize > 0 ) { + // send Frame to the consumer + FramedSource::afterGetting(this); + } + delete frame; +} // end void ZoneMinderDeviceSource::deliverFrame() + +// FrameSource callback on read event +void ZoneMinderDeviceSource::incomingPacketHandler() { + if ( this->getNextFrame() <= 0 ) { + handleClosure(this); + } +} + +// read from monitor +int ZoneMinderDeviceSource::getNextFrame() { + if ( zm_terminate ) + return -1; + + if ( !m_packetqueue_it ) { + m_packetqueue_it = m_packetqueue->get_video_it(true); + } + ZMPacket *zm_packet = m_packetqueue->get_packet(m_packetqueue_it); + while ( zm_packet and (zm_packet->packet.stream_index != m_stream->index) ) { + zm_packet->unlock(); + // We want our stream to start at the same it as the video + // but if this is an audio stream we need to increment past that first packet + Debug(4, "Have audio packet, skipping"); + m_packetqueue->increment_it(m_packetqueue_it, m_stream->index); + zm_packet = m_packetqueue->get_packet(m_packetqueue_it); + } + if ( !zm_packet ) { + Debug(1, "null zm_packet %p", zm_packet); + return -1; + } + // packet is locked + AVPacket *pkt = &zm_packet->packet; + + // Convert pts to timeval + int64_t pts = av_rescale_q(pkt->dts, m_stream->time_base, AV_TIME_BASE_Q); + timeval tv = { pts/1000000, pts%1000000 }; + ZM_DUMP_STREAM_PACKET(m_stream, (*pkt), "rtspServer"); + Debug(2, "pts %" PRId64 " pkt.pts %" PRId64 " tv %d.%d", pts, pkt->pts, tv.tv_sec, tv.tv_usec); + + std::list< std::pair > framesList = this->splitFrames(pkt->data, pkt->size); + zm_packet->unlock(); + zm_packet = nullptr;// we no longer have the lock so shouldn't be accessing it + m_packetqueue->increment_it(m_packetqueue_it, m_stream->index); + + while ( framesList.size() ) { + std::pair nal = framesList.front(); + framesList.pop_front(); + + NAL_Frame *frame = new NAL_Frame(nal.first, nal.second, tv); + + pthread_mutex_lock(&m_mutex); + if ( m_captureQueue.size() ) { + NAL_Frame * f = m_captureQueue.front(); + while ( m_captureQueue.size() and ((f->m_timestamp.tv_sec - tv.tv_sec) > 10) ) { + m_captureQueue.pop_front(); + delete f; + f = m_captureQueue.front(); + } + } +#if 0 + while ( m_captureQueue.size() >= m_queueSize ) { + Debug(2, "Queue full dropping frame %d", m_captureQueue.size()); + NAL_Frame * f = m_captureQueue.front(); + m_captureQueue.pop_front(); + delete f; + } +#endif + m_captureQueue.push_back(frame); + pthread_mutex_unlock(&m_mutex); + + // post an event to ask to deliver the frame + envir().taskScheduler().triggerEvent(m_eventTriggerId, this); + } // end while we get frame from data + return 1; +} + +// split packet in frames +std::list< std::pair > ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSize) { + std::list< std::pair > frameList; + if ( frame != nullptr ) { + frameList.push_back(std::pair(frame, frameSize)); + } + return frameList; +} + +// extract a frame +unsigned char* ZoneMinderDeviceSource::extractFrame(unsigned char* frame, size_t& size, size_t& outsize) { + outsize = size; + size = 0; + return frame; +} +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_device_source.h b/src/zm_rtsp_server_device_source.h new file mode 100644 index 000000000..b74a8a606 --- /dev/null +++ b/src/zm_rtsp_server_device_source.h @@ -0,0 +1,78 @@ +/* --------------------------------------------------------------------------- +** +** DeviceSource.h +** +** live555 source +** +** -------------------------------------------------------------------------*/ + +#ifndef ZM_RTSP_SERVER_DEVICE_SOURCE_H +#define ZM_RTSP_SERVER_DEVICE_SOURCE_H + +#include "zm_config.h" +#include "zm_define.h" +#include "zm_monitor.h" +#include +#include +#include + +#if HAVE_RTSP_SERVER +#include + +class NAL_Frame; + +class ZoneMinderDeviceSource: public FramedSource { + + public: + static ZoneMinderDeviceSource* createNew( + UsageEnvironment& env, + std::shared_ptr monitor, + AVStream * stream, + unsigned int queueSize + ) { + return new ZoneMinderDeviceSource(env, monitor, stream, queueSize); + }; + std::string getAuxLine() { return m_auxLine; }; + int getWidth() { return m_monitor->Width(); }; + int getHeight() { return m_monitor->Height(); }; + + protected: + ZoneMinderDeviceSource(UsageEnvironment& env, std::shared_ptr monitor, AVStream * stream, unsigned int queueSize); + virtual ~ZoneMinderDeviceSource(); + + protected: + static void* threadStub(void* clientData) { return ((ZoneMinderDeviceSource*) clientData)->thread();}; + void* thread(); + static void deliverFrameStub(void* clientData) {((ZoneMinderDeviceSource*) clientData)->deliverFrame();}; + void deliverFrame(); + static void incomingPacketHandlerStub(void* clientData, int mask) { ((ZoneMinderDeviceSource*) clientData)->incomingPacketHandler(); }; + void incomingPacketHandler(); + int getNextFrame(); + void processFrame(char * frame, int frameSize, const timeval &ref); + void queueFrame(char * frame, int frameSize, const timeval &tv); + + // split packet in frames + virtual std::list< std::pair > splitFrames(unsigned char* frame, unsigned frameSize); + + // overide FramedSource + virtual void doGetNextFrame(); + virtual void doStopGettingFrames(); + virtual unsigned char *extractFrame(unsigned char *data, size_t& size, size_t& outsize); + + protected: + std::list m_captureQueue; + EventTriggerId m_eventTriggerId; + AVStream *m_stream; + std::shared_ptr m_monitor; + PacketQueue *m_packetqueue; + std::list::iterator *m_packetqueue_it; + + unsigned int m_queueSize; + pthread_t m_thid; + pthread_mutex_t m_mutex; + std::string m_auxLine; + int stop; +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_DEVICE_SOURCE_H diff --git a/src/zm_rtsp_server_fifo_adts_source.cpp b/src/zm_rtsp_server_fifo_adts_source.cpp new file mode 100644 index 000000000..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_fifo_h264_source.cpp b/src/zm_rtsp_server_fifo_h264_source.cpp new file mode 100644 index 000000000..b96f3da8b --- /dev/null +++ b/src/zm_rtsp_server_fifo_h264_source.cpp @@ -0,0 +1,239 @@ +/* --------------------------------------------------------------------------- +** +** H264_FifoSource.cpp +** +** H264 Live555 source +** +** -------------------------------------------------------------------------*/ + +#include "zm_rtsp_server_fifo_h264_source.h" + +#include "zm_config.h" +#include "zm_logger.h" +#include +#include + +#if HAVE_RTSP_SERVER + +// --------------------------------- +// H264 ZoneMinder FramedSource +// --------------------------------- +// +H264_ZoneMinderFifoSource::H264_ZoneMinderFifoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + 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); + m_hType = 264; +} + +// split packet into frames +std::list< std::pair > H264_ZoneMinderFifoSource::splitFrames(unsigned char* frame, size_t &frameSize) { + std::list< std::pair > frameList; + + size_t bufSize = frameSize; + size_t size = 0; + unsigned char* buffer = this->extractFrame(frame, bufSize, size); + while ( buffer != nullptr ) { +#if 0 + bool updateAux = false; + switch ( m_frameType & 0x1F ) { + case 7: + Debug(4, "SPS_Size: %d bufSize %d", size, bufSize); + m_sps.assign((char*)buffer, size); + updateAux = true; + break; + case 8: + Debug(4, "PPS_Size: %d bufSize %d", size, bufSize); + m_pps.assign((char*)buffer, size); + updateAux = true; + break; + case 5: + Debug(4, "IDR_Size: %d bufSize %d", size, bufSize); + if ( m_repeatConfig && !m_sps.empty() && !m_pps.empty() ) { + frameList.push_back(std::pair((unsigned char*)m_sps.c_str(), m_sps.size())); + frameList.push_back(std::pair((unsigned char*)m_pps.c_str(), m_pps.size())); + } + break; + default: + break; + } + + if ( updateAux and !m_sps.empty() and !m_pps.empty() ) { + u_int32_t profile_level_id = 0; + if ( m_sps.size() >= 4 ) profile_level_id = (m_sps[1]<<16)|(m_sps[2]<<8)|m_sps[3]; + + char* sps_base64 = base64Encode(m_sps.c_str(), m_sps.size()); + char* pps_base64 = base64Encode(m_pps.c_str(), m_pps.size()); + + std::ostringstream os; + os << "profile-level-id=" << std::hex << std::setw(6) << std::setfill('0') << profile_level_id; + os << ";sprop-parameter-sets=" << sps_base64 << "," << pps_base64 << "\r\n"; + if (!(m_width and m_height)) + os << "a=x-dimensions:" << m_width << "," << m_height << "\r\n"; + m_auxLine.assign(os.str()); + Debug(3, "auxLine: %s", m_auxLine.c_str()); + + delete [] sps_base64; + delete [] pps_base64; + } +#endif + frameList.push_back(std::pair(buffer, size)); + if (!bufSize) break; + + buffer = this->extractFrame(&buffer[size], bufSize, size); + } // end while buffer + frameSize = bufSize; + return frameList; +} + +H265_ZoneMinderFifoSource::H265_ZoneMinderFifoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + 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); + m_hType = 265; +} + +// split packet in frames +std::list< std::pair > +H265_ZoneMinderFifoSource::splitFrames(unsigned char* frame, size_t &frameSize) { + std::list< std::pair > frameList; + + size_t bufSize = frameSize; + size_t size = 0; + unsigned char* buffer = this->extractFrame(frame, bufSize, size); + while ( buffer != nullptr ) { +#if 0 + bool updateAux = false; + switch ((m_frameType&0x7E)>>1) { + case 32: + Debug(4, "VPS_Size: %d bufSize %d", size, bufSize); + m_vps.assign((char*)buffer,size); + updateAux = true; + break; + case 33: + Debug(4, "SPS_Size: %d bufSize %d", size, bufSize); + m_sps.assign((char*)buffer,size); + updateAux = true; + break; + case 34: + Debug(4, "PPS_Size: %d bufSize %d", size, bufSize); + m_pps.assign((char*)buffer,size); + updateAux = true; + break; + case 19: + case 20: + Debug(4, "IDR_Size: %d bufSize %d", size, bufSize); + if ( m_repeatConfig && !m_vps.empty() && !m_sps.empty() && !m_pps.empty() ) { + frameList.push_back(std::pair((unsigned char*)m_vps.c_str(), m_vps.size())); + frameList.push_back(std::pair((unsigned char*)m_sps.c_str(), m_sps.size())); + frameList.push_back(std::pair((unsigned char*)m_pps.c_str(), m_pps.size())); + } + break; + default: + //Debug(4, "Unknown frametype!? %d %d", m_frameType, ((m_frameType & 0x7E) >> 1)); + break; + } + + if ( updateAux and !m_vps.empty() and !m_sps.empty() and !m_pps.empty() ) { + char* vps_base64 = base64Encode(m_vps.c_str(), m_vps.size()); + char* sps_base64 = base64Encode(m_sps.c_str(), m_sps.size()); + char* pps_base64 = base64Encode(m_pps.c_str(), m_pps.size()); + + std::ostringstream os; + os << "sprop-vps=" << vps_base64; + os << ";sprop-sps=" << sps_base64; + os << ";sprop-pps=" << pps_base64; + os << "\r\n" << "a=x-dimensions:" << m_width << "," << m_height << "\r\n"; + m_auxLine.assign(os.str()); + Debug(1, "Assigned h265 auxLine to %s", m_auxLine.c_str()); + + delete [] vps_base64; + delete [] sps_base64; + delete [] pps_base64; + } +#endif + frameList.push_back(std::pair(buffer, size)); + + buffer = this->extractFrame(&buffer[size], bufSize, size); + } // end while buffer + frameSize = bufSize; + return frameList; +} // end H265_ZoneMinderFifoSource::splitFrames(unsigned char* frame, unsigned frameSize) + +unsigned char * H26X_ZoneMinderFifoSource::findMarker( + unsigned char *frame, size_t size, size_t &length + ) { + //Debug(1, "findMarker %p %d", frame, size); + unsigned char *start = nullptr; + for ( size_t i = 0; i < size-2; i += 1 ) { + //Debug(1, "%d: %d %d %d", i, frame[i], frame[i+1], frame[i+2]); + if ( (frame[i] == 0) and (frame[i+1]) == 0 and (frame[i+2] == 1) ) { + if ( i and (frame[i-1] == 0) ) { + start = frame + i - 1; + length = sizeof(H264marker); + } else { + start = frame + i; + length = sizeof(H264shortmarker); + } + break; + } + } + return start; +} + +// extract a frame +unsigned char* H26X_ZoneMinderFifoSource::extractFrame(unsigned char* frame, size_t& size, size_t& outsize) { + unsigned char *outFrame = nullptr; + Debug(4, "ExtractFrame: %p %zu", frame, size); + outsize = 0; + size_t markerLength = 0; + m_frameType = 0; + unsigned char *startFrame = nullptr; + if (size >= 3) + startFrame = this->findMarker(frame, size, 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); + unsigned char *endFrame = nullptr; + if ( remainingSize > 3 ) { + endFrame = this->findMarker(startFrame+markerLength, remainingSize, endMarkerLength); + } + Debug(4, "endFrame: %p marker Length %zu, remaining size %d", endFrame, endMarkerLength, remainingSize); + + if ( m_keepMarker ) { + size -= startFrame-frame; + outFrame = startFrame; + } else { + size -= startFrame-frame+markerLength; + outFrame = &startFrame[markerLength]; + } + + if ( endFrame != nullptr ) { + outsize = endFrame - outFrame; + } else { + outsize = size; + } + size -= outsize; + Debug(4, "Have frame type: %d size %zu, keepmarker %d", m_frameType, outsize, m_keepMarker); + } else if ( size >= sizeof(H264shortmarker) ) { + Info("No marker found size %zu", size); + } + + return outFrame; +} +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_fifo_h264_source.h b/src/zm_rtsp_server_fifo_h264_source.h new file mode 100644 index 000000000..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..1845c96ef --- /dev/null +++ b/src/zm_rtsp_server_fifo_source.cpp @@ -0,0 +1,284 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** +** ZoneMinder Live555 source +** +** -------------------------------------------------------------------------*/ + +#include "zm_rtsp_server_fifo_source.h" +#include "zm_rtsp_server_frame.h" + +#include "zm_config.h" +#include "zm_ffmpeg.h" +#include "zm_logger.h" +#include "zm_signal.h" + +#include +#include + +#if HAVE_RTSP_SERVER +ZoneMinderFifoSource::ZoneMinderFifoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + 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 && !stop_) { + 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() and !stop_) { + 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 and !stop_) { + 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 (!stop_ && framesList.size()) { + std::pair nal = framesList.front(); + framesList.pop_front(); + NAL_Frame *Nal = new NAL_Frame(nal.first, nal.second, pts); + m_nalQueue.push(Nal); + } + } + Debug(3, "notifying"); + condition_.notify_all(); + } // end while m_buffer.size() + return 1; +} + +// split packet in frames +std::list< std::pair > ZoneMinderFifoSource::splitFrames(unsigned char* frame, size_t &frameSize) { + std::list< std::pair > frameList; + if ( frame != nullptr ) { + frameList.push_back(std::pair(frame, frameSize)); + } + frameSize = 0; + return frameList; +} + +// extract a frame +unsigned char* ZoneMinderFifoSource::extractFrame(unsigned char* frame, size_t& size, size_t& outsize) { + outsize = size; + size = 0; + return frame; +} +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_fifo_source.h b/src/zm_rtsp_server_fifo_source.h new file mode 100644 index 000000000..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 new file mode 100644 index 000000000..363ac7995 --- /dev/null +++ b/src/zm_rtsp_server_frame.h @@ -0,0 +1,79 @@ +#ifndef ZM_RTSP_SERVER_FRAME_H +#define ZM_RTSP_SERVER_FRAME_H + +#include "zm_config.h" +#include "zm_logger.h" +#include +#include + +#if HAVE_RTSP_SERVER +// --------------------------------- +// Captured frame +// --------------------------------- +const char H264marker[] = {0,0,0,1}; +const char H264shortmarker[] = {0,0,1}; + +class NAL_Frame { + public: + NAL_Frame(unsigned char * buffer, size_t size, int64 pts) : + m_buffer(nullptr), + m_size(size), + m_pts(pts), + m_ref_count(1) { + m_buffer = new unsigned char[m_size]; + if (buffer) { + memcpy(m_buffer, buffer, m_size); + } + }; + NAL_Frame& operator=(const NAL_Frame&); + ~NAL_Frame() { + delete[] m_buffer; + m_buffer = nullptr; + }; + unsigned char *buffer() const { return m_buffer; }; + // The buffer has a 32bit nal size value at the front, so if we want the nal, it's + // the address of the buffer plus 4 bytes. + unsigned char *nal() const { return m_buffer+4; }; + size_t size() const { return m_size; }; + size_t size(size_t new_size) { m_size=new_size; return m_size; }; + size_t nal_size() const { return m_size-4; }; + int64_t pts() const { return m_pts; }; + bool check() const { + // Look for marker at beginning + unsigned char *marker = (unsigned char*)memmem(m_buffer, sizeof(H264marker), H264marker, sizeof(H264marker)); + if ( marker ) { + Debug(1, "marker found at beginning"); + return true; + } else { + marker = (unsigned char*)memmem(m_buffer, m_size, H264marker, sizeof(H264marker)); + if ( marker ) { + Debug(1, "marker not found at beginning"); + return false; + } + } + return false; + } + + void debug() { + if (m_size <= 4) { + Debug(1, "NAL: %zu: %.2x %.2x %.2x %.2x", m_size, + m_buffer[0], m_buffer[1], m_buffer[2], m_buffer[3]); + } else { + Debug(1, "NAL: %zu: %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x ", m_size, + m_buffer[0], m_buffer[1], m_buffer[2], m_buffer[3], + m_buffer[4], m_buffer[5], m_buffer[6], m_buffer[7] + ); + } + } + + private: + unsigned char* m_buffer; + size_t m_size; + public: + int64 m_pts; + private: + int m_ref_count; +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_FRAME_H diff --git a/src/zm_rtsp_server_server_media_subsession.cpp b/src/zm_rtsp_server_server_media_subsession.cpp new file mode 100644 index 000000000..c847b471e --- /dev/null +++ b/src/zm_rtsp_server_server_media_subsession.cpp @@ -0,0 +1,109 @@ +/* --------------------------------------------------------------------------- +** +** ServerMediaSubsession.cpp +** +** -------------------------------------------------------------------------*/ + +#include "zm_rtsp_server_server_media_subsession.h" + +#include "zm_config.h" +#include "zm_rtsp_server_adts_source.h" +#include "zm_rtsp_server_adts_fifo_source.h" +#include + +#if HAVE_RTSP_SERVER +// --------------------------------- +// BaseServerMediaSubsession +// --------------------------------- +FramedSource* BaseServerMediaSubsession::createSource( + UsageEnvironment& env, + FramedSource* inputSource, + const std::string& format) +{ + FramedSource* source = nullptr; + if (format == "video/MP2T") { + source = MPEG2TransportStreamFramer::createNew(env, inputSource); + } else if (format == "video/H264") { + source = H264VideoStreamDiscreteFramer::createNew(env, inputSource + /*Boolean includeStartCodeInOutput, Boolean insertAccessUnitDelimiters*/ + ); + } +#if LIVEMEDIA_LIBRARY_VERSION_INT > 1414454400 + else if (format == "video/H265") { + source = H265VideoStreamDiscreteFramer::createNew(env, inputSource); + } +#endif +#if 0 + else if (format == "video/JPEG") { + source = MJPEGVideoSource::createNew(env, inputSource); + } +#endif + else { + source = inputSource; + } + return source; +} + +/* source is generally a replica */ +RTPSink* BaseServerMediaSubsession::createSink( + UsageEnvironment& env, + Groupsock* rtpGroupsock, + unsigned char rtpPayloadTypeIfDynamic, + const std::string& format, + FramedSource *source + ) { + + RTPSink* sink = nullptr; + if (format == "video/MP2T") { + sink = SimpleRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic, 90000, "video", "MP2T", 1, true, false); + } else if (format == "video/H264") { + sink = H264VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic); + } else if (format == "video/VP8") { + sink = VP8VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic); + } +#if LIVEMEDIA_LIBRARY_VERSION_INT > 1414454400 + else if (format == "video/VP9") { + sink = VP9VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic); + } else if (format == "video/H265") { + sink = H265VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic); + } +#endif + else if (format == "audio/AAC") { + ADTS_ZoneMinderFifoSource *adts_source = (ADTS_ZoneMinderFifoSource *)(m_replicator->inputSource()); + sink = MPEG4GenericRTPSink::createNew(env, rtpGroupsock, + rtpPayloadTypeIfDynamic, + adts_source->getFrequency(), + "audio", "AAC-hbr", + adts_source->configStr(), + adts_source->getChannels() + ); + } else { + Error("unknown format"); + } +#if 0 + else if (format == "video/JPEG") { + sink = JPEGVideoRTPSink::createNew (env, rtpGroupsock); + } +#endif + return sink; +} + +char const* BaseServerMediaSubsession::getAuxLine( + ZoneMinderFifoSource* source, + unsigned char rtpPayloadType + ) { + const char* auxLine = nullptr; + if (source) { + std::ostringstream os; + os << "a=fmtp:" << int(rtpPayloadType) << " "; + os << source->getAuxLine(); + //os << "\r\n"; + auxLine = strdup(os.str().c_str()); + Debug(1, "BaseServerMediaSubsession::auxLine: %s", auxLine); + } else { + Error("No source auxLine:"); + return ""; + } + return auxLine; +} +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_server_media_subsession.h b/src/zm_rtsp_server_server_media_subsession.h new file mode 100644 index 000000000..04632c44b --- /dev/null +++ b/src/zm_rtsp_server_server_media_subsession.h @@ -0,0 +1,48 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** ServerMediaSubsession.h +** +** -------------------------------------------------------------------------*/ + +#ifndef ZM_RTSP_SERVER_SERVER_MEDIA_SUBSESSION_H +#define ZM_RTSP_SERVER_SERVER_MEDIA_SUBSESSION_H + +#include "zm_config.h" +#include "zm_rtsp_server_fifo_source.h" +#include + +#if HAVE_RTSP_SERVER +#include + +class ZoneMinderDeviceSource; + +class BaseServerMediaSubsession { + public: + explicit BaseServerMediaSubsession(StreamReplicator* replicator): + m_replicator(replicator) {}; + + FramedSource* createSource( + UsageEnvironment& env, + FramedSource * videoES, + const std::string& format); + + RTPSink * createSink( + UsageEnvironment& env, + Groupsock * rtpGroupsock, + unsigned char rtpPayloadTypeIfDynamic, + const std::string& format, + FramedSource *source); + + char const* getAuxLine( + ZoneMinderFifoSource* source, + unsigned char rtpPayloadType); + + protected: + StreamReplicator* m_replicator; +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_SERVER_MEDIA_SUBSESSION_H diff --git a/src/zm_rtsp_server_thread.cpp b/src/zm_rtsp_server_thread.cpp new file mode 100644 index 000000000..cbe7b905b --- /dev/null +++ b/src/zm_rtsp_server_thread.cpp @@ -0,0 +1,128 @@ +#include "zm_rtsp_server_thread.h" + +#include "zm_config.h" +#include "zm_logger.h" + +#if HAVE_RTSP_SERVER + +RTSPServerThread::RTSPServerThread(int p_port) : + terminate_(false), scheduler_watch_var_(0), port(p_port) +{ + //unsigned short rtsp_over_http_port = 0; + //const char *realm = "ZoneMinder"; + // + eventLoop = std::make_shared(); + rtspServer = xop::RtspServer::Create(eventLoop.get()); + + if ( rtspServer == nullptr ) { + Fatal("Failed to create rtspServer"); + return; + } + + thread_ = std::thread(&RTSPServerThread::Run, this); +} + +RTSPServerThread::~RTSPServerThread() { + Stop(); + if (thread_.joinable()) + thread_.join(); + +} + +void RTSPServerThread::Run() { + Debug(1, "RTSPServerThread::Run()"); + if (rtspServer) { + while (!scheduler_watch_var_) { + //if (clients > 0) { + sleep(1); + //} + } + } + Debug(1, "RTSPServerThread::done()"); +} + +int RTSPServerThread::Start() { + return rtspServer->Start(std::string("0.0.0.0"), port); +} + +void RTSPServerThread::Stop() { + Debug(1, "RTSPServerThread::stop()"); + terminate_ = true; + + { + std::lock_guard lck(scheduler_watch_var_mutex_); + scheduler_watch_var_ = 1; + } + + for ( std::list::iterator it = sources.begin(); it != sources.end(); ++it ) { + Debug(1, "RTSPServerThread::stopping source"); + (*it)->Stop(); + } + while ( sources.size() ) { + Debug(1, "RTSPServerThread::stop closing source"); + ZoneMinderFifoSource *source = sources.front(); + sources.pop_front(); + delete source; + } +} + +xop::MediaSession *RTSPServerThread::addSession(std::string &streamname) { + + xop::MediaSession *session = xop::MediaSession::CreateNew(streamname); + if (session) { + session->AddNotifyConnectedCallback([] (xop::MediaSessionId sessionId, std::string peer_ip, uint16_t peer_port){ + Debug(1, "RTSP client connect, ip=%s, port=%hu \n", peer_ip.c_str(), peer_port); +}); + + session->AddNotifyDisconnectedCallback([](xop::MediaSessionId sessionId, std::string peer_ip, uint16_t peer_port) { + Debug(1, "RTSP client disconnect, ip=%s, port=%hu \n", peer_ip.c_str(), peer_port); +}); + + rtspServer->AddSession(session); + //char *url = rtspServer->rtspURL(session); + //Debug(1, "url is %s for stream %s", url, streamname.c_str()); + //delete[] url; + } + return session; +} + +void RTSPServerThread::removeSession(xop::MediaSession *session) { + //rtspServer->removeServerMediaSession(session); +} + +ZoneMinderFifoSource *RTSPServerThread::addFifo( + xop::MediaSession *session, + std::string fifo) { + if (!rtspServer) return nullptr; + + ZoneMinderFifoSource *source = nullptr; + + if (!fifo.empty()) { + std::string rtpFormat; + if (std::string::npos != fifo.find("h264")) { + rtpFormat = "video/H264"; + session->AddSource(xop::channel_0, xop::H264Source::CreateNew()); + source = new ZoneMinderFifoSource(rtspServer, session->GetMediaSessionId(), xop::channel_0, fifo); + } else if ( + std::string::npos != fifo.find("hevc") + or + std::string::npos != fifo.find("h265")) { + rtpFormat = "video/H265"; + session->AddSource(xop::channel_0, xop::H265Source::CreateNew()); + source = new ZoneMinderFifoSource(rtspServer, session->GetMediaSessionId(), xop::channel_0, fifo); + } else if (std::string::npos != fifo.find("aac")) { + Debug(1, "ADTS source %p", source); + } else { + Warning("Unknown format in %s", fifo.c_str()); + } + if (source == nullptr) { + Error("Unable to create source"); + } + sources.push_back(source); + } else { + Debug(1, "Not Adding stream as fifo was empty"); + } + return source; +} // end void addFifo + +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_thread.h b/src/zm_rtsp_server_thread.h new file mode 100644 index 000000000..393203831 --- /dev/null +++ b/src/zm_rtsp_server_thread.h @@ -0,0 +1,45 @@ +#ifndef ZM_RTSP_SERVER_THREAD_H +#define ZM_RTSP_SERVER_THREAD_H + +#include "zm_config.h" +#include "zm_ffmpeg.h" +#include "xop/RtspServer.h" + +#include "zm_rtsp_server_fifo_source.h" +#include +#include +#include + +#if HAVE_RTSP_SERVER + +class Monitor; + +class RTSPServerThread { + private: + std::shared_ptr monitor_; + + std::thread thread_; + std::atomic terminate_; + std::mutex scheduler_watch_var_mutex_; + char scheduler_watch_var_; + + std::shared_ptr eventLoop; + std::shared_ptr rtspServer; + + std::list sources; + int port; + + public: + explicit RTSPServerThread(int port); + ~RTSPServerThread(); + xop::MediaSession *addSession(std::string &streamname); + void removeSession(xop::MediaSession *sms); + ZoneMinderFifoSource *addFifo(xop::MediaSession *sms, std::string fifo); + void Run(); + void Stop(); + int Start(); + bool IsStopped() const { return terminate_; }; +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_THREAD_H diff --git a/src/zm_rtsp_server_unicast_server_media_subsession.cpp b/src/zm_rtsp_server_unicast_server_media_subsession.cpp new file mode 100644 index 000000000..2376748d5 --- /dev/null +++ b/src/zm_rtsp_server_unicast_server_media_subsession.cpp @@ -0,0 +1,52 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** ServerMediaSubsession.cpp +** +** -------------------------------------------------------------------------*/ + +#include "zm_rtsp_server_unicast_server_media_subsession.h" + +#include "zm_config.h" +#include "zm_rtsp_server_fifo_source.h" + +#if HAVE_RTSP_SERVER +// ----------------------------------------- +// ServerMediaSubsession for Unicast +// ----------------------------------------- +UnicastServerMediaSubsession* UnicastServerMediaSubsession::createNew( + UsageEnvironment& env, + StreamReplicator* replicator, + //FramedSource *source, + const std::string& format + ) { + return new UnicastServerMediaSubsession(env, replicator, format); + //return new UnicastServerMediaSubsession(env, replicator, source, format); +} + +FramedSource* UnicastServerMediaSubsession::createNewStreamSource( + unsigned clientSessionId, + unsigned& estBitrate + ) { + FramedSource* replica = m_replicator->createStreamReplica(); + return createSource(envir(), replica, m_format); +} + +RTPSink* UnicastServerMediaSubsession::createNewRTPSink( + Groupsock* rtpGroupsock, + unsigned char rtpPayloadTypeIfDynamic, + FramedSource* inputSource + ) { + return createSink(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic, m_format, inputSource); +} + +char const* UnicastServerMediaSubsession::getAuxSDPLine( + RTPSink* rtpSink, FramedSource* inputSource + ) { + return this->getAuxLine( + dynamic_cast(m_replicator->inputSource()), + rtpSink->rtpPayloadType()); +} +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_unicast_server_media_subsession.h b/src/zm_rtsp_server_unicast_server_media_subsession.h new file mode 100644 index 000000000..d10e3abf1 --- /dev/null +++ b/src/zm_rtsp_server_unicast_server_media_subsession.h @@ -0,0 +1,51 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** ServerMediaSubsession.h +** +** -------------------------------------------------------------------------*/ + +#ifndef ZM_RTSP_SERVER_UNICAST_SERVER_MEDIA_SUBSESSION_H +#define ZM_RTSP_SERVER_UNICAST_SERVER_MEDIA_SUBSESSION_H + +#include "zm_config.h" +#include "zm_rtsp_server_server_media_subsession.h" + +// ----------------------------------------- +// ServerMediaSubsession for Unicast +// ----------------------------------------- +#if HAVE_RTSP_SERVER +class UnicastServerMediaSubsession : + public OnDemandServerMediaSubsession, + public BaseServerMediaSubsession +{ + public: + static UnicastServerMediaSubsession* createNew( + UsageEnvironment& env, + StreamReplicator* replicator, + const std::string& format); + + protected: + UnicastServerMediaSubsession( + UsageEnvironment& env, + StreamReplicator* replicator, + const std::string& format) + : + OnDemandServerMediaSubsession(env, true + /* Boolean reuseFirstSource, portNumBits initialPortNum=6970, Boolean multiplexRTCPWithRTP=False */ + ), + BaseServerMediaSubsession(replicator), + m_format(format) {}; + + virtual FramedSource* createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate); + virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource); + virtual char const* getAuxSDPLine(RTPSink* rtpSink, FramedSource* inputSource); + + protected: + const std::string m_format; +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_UNICAST_SERVER_MEDIA_SUBSESSION_H diff --git a/src/zm_sdp.cpp b/src/zm_sdp.cpp index c7bf1b578..bdbc9e1d1 100644 --- a/src/zm_sdp.cpp +++ b/src/zm_sdp.cpp @@ -17,13 +17,12 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" - -#if HAVE_LIBAVFORMAT - #include "zm_sdp.h" -#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) +#include "zm_config.h" +#include "zm_exception.h" +#include "zm_logger.h" + 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 }, @@ -61,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]; @@ -114,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]; @@ -127,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]; @@ -152,18 +112,18 @@ SessionDescriptor::MediaDescriptor::MediaDescriptor( mWidth( 0 ), mHeight( 0 ), mSprops( "" ), - mConnInfo( 0 ) + mConnInfo( nullptr ) { } SessionDescriptor::SessionDescriptor( const std::string &url, const std::string &sdp ) : mUrl( url ), - mConnInfo( 0 ), - mBandInfo( 0 ) + mConnInfo( nullptr ), + mBandInfo( nullptr ) { - MediaDescriptor *currMedia = 0; + MediaDescriptor *currMedia = nullptr; - StringVector lines = split( sdp, "\r\n" ); + StringVector lines = Split(sdp, "\r\n"); for ( StringVector::const_iterator iter = lines.begin(); iter != lines.end(); ++iter ) { std::string line = *iter; if ( line.empty() ) @@ -206,7 +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" ) { @@ -218,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 ); @@ -235,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); @@ -255,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" ) { @@ -274,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()); } } } @@ -292,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 ) @@ -341,88 +301,54 @@ 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, NULL); + 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(NULL); - avcodec_parameters_to_context(codec_context, stream->codecpar); - stream->codec = codec_context; -#else - AVCodecContext *codec_context = stream->codec; -#endif + AVCodecContext *codec_context = avcodec_alloc_context3(nullptr); 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; } } } /// 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() ); - //return( 0 ); } if ( mediaDesc->getWidth() ) codec_context->width = mediaDesc->getWidth(); @@ -431,12 +357,12 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { if ( codec_context->codec_id == AV_CODEC_ID_H264 && mediaDesc->getSprops().size()) { uint8_t start_sequence[]= { 0, 0, 1 }; codec_context->extradata_size= 0; - codec_context->extradata= NULL; + codec_context->extradata= nullptr; char pvalue[1024], *value = pvalue; strcpy(pvalue, mediaDesc->getSprops().c_str()); - while (*value) { + while ( *value ) { char base64packet[1024]; uint8_t decoded_packet[1024]; uint32_t packet_size; @@ -451,18 +377,12 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { if ( *value == ',' ) value++; - packet_size= av_base64_decode(decoded_packet, (const char *)base64packet, (int)sizeof(decoded_packet)); + packet_size = av_base64_decode(decoded_packet, (const char *)base64packet, (int)sizeof(decoded_packet)); Hexdump(4, (char *)decoded_packet, packet_size); - if (packet_size) { - 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 -); + if ( packet_size ) { + 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? @@ -474,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; @@ -490,9 +406,8 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { } } } - } + avcodec_parameters_from_context(stream->codecpar, codec_context); + } // end foreach mediaList return formatContext; } - -#endif // HAVE_LIBAVFORMAT diff --git a/src/zm_sdp.h b/src/zm_sdp.h index ae7cd67a2..68f7e30a0 100644 --- a/src/zm_sdp.h +++ b/src/zm_sdp.h @@ -20,14 +20,8 @@ #ifndef ZM_SDP_H #define ZM_SDP_H -#include "zm.h" - -#include "zm_utils.h" -#include "zm_exception.h" #include "zm_ffmpeg.h" - -#include - +#include "zm_utils.h" #include #include @@ -38,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; @@ -50,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; @@ -120,7 +106,7 @@ public: { return( mTransport ); } - const int getPayloadType() const + int getPayloadType() const { return( mPayloadType ); } @@ -142,7 +128,7 @@ public: mControlUrl = controlUrl; } - const int getClock() const { + int getClock() const { return( mClock ); } void setClock( int clock ) { @@ -163,10 +149,10 @@ public: void setSprops(const std::string &props) { mSprops = props; } - const std::string getSprops() const { + std::string getSprops() const { return ( mSprops ); } - const double getFrameRate() const { + double getFrameRate() const { return( mFrameRate ); } void setFrameRate( double frameRate ) { @@ -211,7 +197,7 @@ public: MediaDescriptor *getStream( int index ) { if ( index < 0 || (unsigned int)index >= mMediaList.size() ) - return( 0 ); + return nullptr; return( mMediaList[index] ); } diff --git a/src/zm_sendfile.h b/src/zm_sendfile.h index 2f892d693..fd72e2e61 100644 --- a/src/zm_sendfile.h +++ b/src/zm_sendfile.h @@ -1,17 +1,13 @@ #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) { int err; err = sendfile(out_fd, in_fd, offset, size); - if (err < 0) + if ( err < 0 ) return -errno; return err; @@ -22,7 +18,7 @@ int zm_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) { #include int zm_sendfile(int out_fd, int in_fd, off_t *offset, off_t size) { int err; - err = sendfile(in_fd, out_fd, *offset, size, NULL, &size, 0); + err = sendfile(in_fd, out_fd, *offset, size, nullptr, &size, 0); if (err && errno != EAGAIN) return -errno; @@ -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 e13bfdc9c..4107e96c8 100644 --- a/src/zm_signal.cpp +++ b/src/zm_signal.cpp @@ -17,27 +17,24 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" #include "zm_signal.h" -#include -#include -#include +#include "zm.h" +#include "zm_logger.h" +#include #define TRACE_SIZE 16 bool zm_reload = false; bool zm_terminate = false; -RETSIGTYPE zm_hup_handler(int signal) -{ +RETSIGTYPE zm_hup_handler(int signal) { // Shouldn't do complex things in signal handlers, logging is complex and can block due to mutexes. //Info("Got signal %d (%s), reloading", signal, strsignal(signal)); zm_reload = true; } -RETSIGTYPE zm_term_handler(int signal) -{ +RETSIGTYPE zm_term_handler(int signal) { // Shouldn't do complex things in signal handlers, logging is complex and can block due to mutexes. //Info("Got signal %d (%s), exiting", signal, strsignal(signal)); zm_terminate = true; @@ -49,14 +46,14 @@ 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 #if ( HAVE_SIGINFO_T && HAVE_UCONTEXT_T ) - void *ip = 0; - void *cr2 = 0; - if (info && context) { - + void *ip = nullptr; + void *cr2 = nullptr; + if ( info && context ) { Debug(1, "Signal information: number %d code %d errno %d pid %d uid %d status %d", signal, info->si_code, info->si_errno, info->si_pid, @@ -79,7 +76,7 @@ RETSIGTYPE zm_die_handler(int signal) #endif // defined(__x86_64__) // Print the signal address and instruction pointer if available - if (ip) { + if ( ip ) { Error("Signal address is %p, from %p", cr2, ip); } else { Error("Signal address is %p, no instruction pointer", cr2); @@ -108,15 +105,13 @@ RETSIGTYPE zm_die_handler(int signal) } free(messages); - Info("Backtrace complete, please execute the following command for more information"); - Info(cmd); + Info("Backtrace complete, please execute the following command for more information: %s", cmd); #endif // ( !defined(ZM_NO_CRASHTRACE) && HAVE_DECL_BACKTRACE && HAVE_DECL_BACKTRACE_SYMBOLS ) #endif // (defined(__i386__) || defined(__x86_64__) exit(signal); } -void zmSetHupHandler(SigHandler * handler) -{ +void zmSetHupHandler(SigHandler * handler) { sigset_t block_set; sigemptyset(&block_set); struct sigaction action, old_action; @@ -127,8 +122,7 @@ void zmSetHupHandler(SigHandler * handler) sigaction(SIGHUP, &action, &old_action); } -void zmSetTermHandler(SigHandler * handler) -{ +void zmSetTermHandler(SigHandler * handler) { sigset_t block_set; sigemptyset(&block_set); struct sigaction action, old_action; @@ -141,8 +135,7 @@ void zmSetTermHandler(SigHandler * handler) sigaction(SIGQUIT, &action, &old_action); } -void zmSetDieHandler(SigHandler * handler) -{ +void zmSetDieHandler(SigHandler * handler) { sigset_t block_set; sigemptyset(&block_set); struct sigaction action, old_action; @@ -163,19 +156,16 @@ void zmSetDieHandler(SigHandler * handler) sigaction(SIGFPE, &action, &old_action); } -void zmSetDefaultHupHandler() -{ +void zmSetDefaultHupHandler() { zmSetHupHandler((SigHandler *) zm_hup_handler); } -void zmSetDefaultTermHandler() -{ +void zmSetDefaultTermHandler() { zmSetTermHandler((SigHandler *) zm_term_handler); } -void zmSetDefaultDieHandler() -{ - if (config.dump_cores) { +void zmSetDefaultDieHandler() { + if ( config.dump_cores ) { // Do nothing } else { zmSetDieHandler((SigHandler *) zm_die_handler); diff --git a/src/zm_signal.h b/src/zm_signal.h index 89a4b408a..27fa2e766 100644 --- a/src/zm_signal.h +++ b/src/zm_signal.h @@ -20,7 +20,8 @@ #ifndef ZM_SIGNAL_H #define ZM_SIGNAL_H -#include +#include "zm_config.h" +#include #if HAVE_EXECINFO_H #include @@ -29,9 +30,6 @@ #include #endif - -#include "zm.h" - typedef RETSIGTYPE (SigHandler)( int ); extern bool zm_reload; diff --git a/src/zm_storage.cpp b/src/zm_storage.cpp index 4bba82bab..e53e79335 100644 --- a/src/zm_storage.cpp +++ b/src/zm_storage.cpp @@ -15,41 +15,39 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "zm.h" -#include "zm_db.h" +*/ #include "zm_storage.h" -#include -#include -#include +#include "zm_db.h" +#include "zm_logger.h" +#include "zm_utils.h" +#include -Storage::Storage() { - Warning("Instantiating default Storage Object. Should not happen."); - id = 0; - strcpy(name, "Default"); - if ( staticConfig.DIR_EVENTS[0] != '/' ) { +Storage::Storage() : id(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() ); + snprintf(path, sizeof(path), "%s/%s", + staticConfig.PATH_WEB.c_str(), staticConfig.DIR_EVENTS.c_str()); } else { - strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path)-1 ); + strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path) - 1); } scheme = MEDIUM; scheme_str = "Medium"; } -Storage::Storage( MYSQL_ROW &dbrow ) { - unsigned int index = 0; - id = atoi( dbrow[index++] ); - strncpy( name, dbrow[index++], sizeof(name)-1 ); - strncpy( path, dbrow[index++], sizeof(path)-1 ); +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); 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; @@ -57,45 +55,43 @@ Storage::Storage( MYSQL_ROW &dbrow ) { } /* If a zero or invalid p_id is passed, then the old default path will be assumed. */ -Storage::Storage( unsigned int p_id ) { - id = 0; - - if ( p_id ) { - char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), "SELECT `Id`, `Name`, `Path`, `Type`, `Scheme` FROM `Storage` WHERE `Id`=%d", p_id); - Debug(2,"Loading Storage for %d using %s", p_id, sql ); - zmDbRow dbrow; - if ( !dbrow.fetch(sql) ) { - Error("Unable to load storage area for id %d: %s", p_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); +Storage::Storage(unsigned int p_id) : id(p_id) { + 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, this->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()); - } else { - strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path)-1); + Debug(1, "Loaded Storage area %d '%s'", id, name); } - Debug(1,"No id passed to Storage constructor. Using default path %s instead", path); - strcpy(name, "Default"); + } + 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()); + } else { + strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path) - 1); + } + Debug(1, "No id passed to Storage constructor. Using default path %s instead", path); + strcpy(name, "Default"); scheme = MEDIUM; scheme_str = "Medium"; - } + } } Storage::~Storage() { diff --git a/src/zm_storage.h b/src/zm_storage.h index f0886c9f3..6ea0bae5a 100644 --- a/src/zm_storage.h +++ b/src/zm_storage.h @@ -15,13 +15,14 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "zm_db.h" +*/ #ifndef ZM_STORAGE_H #define ZM_STORAGE_H +#include "zm_db.h" +#include + class Storage { public: typedef enum { @@ -47,8 +48,8 @@ public: unsigned int Id() const { return id; } const char *Name() const { return name; } const char *Path() const { return path; } - const Schemes Scheme() const { return scheme; } - const std::string SchemeString() const { return scheme_str; } + Schemes Scheme() const { return scheme; } + std::string SchemeString() const { return scheme_str; } }; #endif // ZM_STORAGE_H diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index ee874cf64..084bd6754 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -17,35 +17,47 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include -#include -#include -#include -#include - -#include "zm.h" -#include "zm_mpeg.h" -#include "zm_monitor.h" - #include "zm_stream.h" +#include "zm_box.h" +#include "zm_monitor.h" +#include +#include +#include +#include +#include + +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 = NULL; + vid_stream = nullptr; } -#endif closeComms(); } -bool StreamBase::loadMonitor(int monitor_id) { - if ( !(monitor = Monitor::Load(monitor_id, false, Monitor::QUERY)) ) { +bool StreamBase::loadMonitor(int p_monitor_id) { + monitor_id = p_monitor_id; + + if ( !(monitor or (monitor = Monitor::Load(monitor_id, false, Monitor::QUERY))) ) { Error("Unable to load monitor id %d for streaming", monitor_id); return false; } - if ( ! monitor->connect() ) { + + 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->isConnected() ) { + monitor->disconnect(); + } + + if ( !monitor->connect() ) { Error("Unable to connect to monitor id %d for streaming", monitor_id); + monitor->disconnect(); return false; } @@ -53,8 +65,20 @@ bool StreamBase::loadMonitor(int monitor_id) { } bool StreamBase::checkInitialised() { - if ( !monitor ) { - Fatal("Cannot stream, not initialised"); + if (!monitor) { + Error("Cannot stream, not initialised"); + return false; + } + 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()) { + 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; @@ -62,7 +86,7 @@ bool StreamBase::checkInitialised() { void StreamBase::updateFrameRate(double fps) { frame_mod = 1; - if ( (fps < 0) || !fps || isinf(fps) ) { + if ( (fps < 0) || !fps || std::isinf(fps) ) { Debug(1, "Zero or negative fps %f in updateFrameRate. Setting frame_mod=1 and effective_fps=0.0", fps); effective_fps = 0.0; base_fps = 0.0; @@ -103,13 +127,16 @@ bool StreamBase::checkCommandQueue() { processCommand(&msg); return true; } + } else if ( connkey ) { + Warning("No sd in checkCommandQueue, comms not open for connkey %06d?", connkey); } else { - Warning("No sd in checkCommandQueue, comms not open?"); + // Perfectly valid if only getting a snapshot + Debug(1, "No sd in checkCommandQueue, comms not open."); } return false; -} +} // end bool StreamBase::checkCommandQueue() -Image *StreamBase::prepareImage( Image *image ) { +Image *StreamBase::prepareImage(Image *image) { // Do not bother to scale zoomed in images, just crop them and let the browser scale // Works in FF2 but breaks FF3 which doesn't like image sizes changing in mid stream. @@ -119,50 +146,48 @@ Image *StreamBase::prepareImage( Image *image ) { int mag = (scale * zoom) / ZM_SCALE_BASE; int act_mag = optimisedScaling?(mag > ZM_SCALE_BASE?ZM_SCALE_BASE:mag):mag; - Debug( 3, "Scaling by %d, zooming by %d = magnifying by %d(%d)", scale, zoom, mag, act_mag ); int last_mag = (last_scale * last_zoom) / ZM_SCALE_BASE; int last_act_mag = last_mag > ZM_SCALE_BASE?ZM_SCALE_BASE:last_mag; - Debug( 3, "Last scaling by %d, zooming by %d = magnifying by %d(%d)", last_scale, last_zoom, last_mag, last_act_mag ); - int base_image_width = image->Width(), base_image_height = image->Height(); - Debug( 3, "Base image width = %d, height = %d", base_image_width, base_image_height ); - int virt_image_width = (base_image_width * mag) / ZM_SCALE_BASE, virt_image_height = (base_image_height * mag) / ZM_SCALE_BASE; - Debug( 3, "Virtual image width = %d, height = %d", virt_image_width, virt_image_height ); - int last_virt_image_width = (base_image_width * last_mag) / ZM_SCALE_BASE, last_virt_image_height = (base_image_height * last_mag) / ZM_SCALE_BASE; - Debug( 3, "Last virtual image width = %d, height = %d", last_virt_image_width, last_virt_image_height ); - int act_image_width = (base_image_width * act_mag ) / ZM_SCALE_BASE, act_image_height = (base_image_height * act_mag ) / ZM_SCALE_BASE; - Debug( 3, "Actual image width = %d, height = %d", act_image_width, act_image_height ); - int last_act_image_width = (base_image_width * last_act_mag ) / ZM_SCALE_BASE, last_act_image_height = (base_image_height * last_act_mag ) / ZM_SCALE_BASE; - Debug( 3, "Last actual image width = %d, height = %d", last_act_image_width, last_act_image_height ); - int disp_image_width = (image->Width() * scale) / ZM_SCALE_BASE, disp_image_height = (image->Height() * scale) / ZM_SCALE_BASE; - Debug( 3, "Display image width = %d, height = %d", disp_image_width, disp_image_height ); - int last_disp_image_width = (image->Width() * last_scale) / ZM_SCALE_BASE, last_disp_image_height = (image->Height() * last_scale) / ZM_SCALE_BASE; - Debug( 3, "Last display image width = %d, height = %d", last_disp_image_width, last_disp_image_height ); - int send_image_width = (disp_image_width * act_mag ) / mag, send_image_height = (disp_image_height * act_mag ) / mag; - Debug( 3, "Send image width = %d, height = %d", send_image_width, send_image_height ); - int last_send_image_width = (last_disp_image_width * last_act_mag ) / last_mag, last_send_image_height = (last_disp_image_height * last_act_mag ) / last_mag; - Debug( 3, "Last send image width = %d, height = %d", last_send_image_width, last_send_image_height ); + Debug(3, + "Scaling by %d, zooming by %d = magnifying by %d(%d)\n" + "Last scaling by %d, zooming by %d = magnifying by %d(%d)\n" + "Base image width = %d, height = %d\n" + "Virtual image width = %d, height = %d\n" + "Last virtual image width = %d, height = %d\n" + "Actual image width = %d, height = %d\n" + "Last actual image width = %d, height = %d\n" + "Display image width = %d, height = %d\n" + "Last display image width = %d, height = %d\n" + "Send image width = %d, height = %d\n", + scale, zoom, mag, act_mag, + last_scale, last_zoom, last_mag, last_act_mag, + base_image_width, base_image_height, + virt_image_width, virt_image_height, + last_virt_image_width, last_virt_image_height, + act_image_width, act_image_height, + last_act_image_width, last_act_image_height, + disp_image_width, disp_image_height, + last_disp_image_width, last_disp_image_height, + send_image_width, send_image_height + ); - if ( mag != ZM_SCALE_BASE ) { - if ( act_mag != ZM_SCALE_BASE ) { - Debug(3, "Magnifying by %d", mag); - if ( !image_copied ) { - static Image copy_image; - copy_image.Assign(*image); - image = ©_image; - image_copied = true; - } - image->Scale(mag); - } + if ( ( mag != ZM_SCALE_BASE ) && (act_mag != ZM_SCALE_BASE) ) { + Debug(3, "Magnifying by %d", mag); + static Image copy_image; + copy_image.Assign(*image); + image = ©_image; + image_copied = true; + image->Scale(mag); } Debug(3, "Real image width = %d, height = %d", image->Width(), image->Height()); @@ -171,26 +196,22 @@ Image *StreamBase::prepareImage( Image *image ) { static Box last_crop; if ( mag != last_mag || x != last_x || y != last_y ) { - Debug( 3, "Got click at %d,%d x %d", x, y, mag ); - - //if ( !last_mag ) - //last_mag = mag; + Debug(3, "Got click at %d,%d x %d", x, y, mag); if ( !(last_disp_image_width < last_virt_image_width || last_disp_image_height < last_virt_image_height) ) last_crop = Box(); - Debug( 3, "Recalculating crop" ); // 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; - Debug( 3, "Got adjusted click at %d%%,%d%%", click_x, click_y ); + 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 click_x = ( click_x * act_image_width ) / 100; click_y = ( click_y * act_image_height ) / 100; - Debug( 3, "Got readjusted click at %d,%d", click_x, click_y ); + Debug(3, "Got readjusted click at %d,%d", click_x, click_y); int lo_x = click_x - (send_image_width/2); if ( lo_x < 0 ) @@ -208,61 +229,76 @@ 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 ); - } - Debug( 3, "Cropping to %d,%d -> %d,%d", last_crop.LoX(), last_crop.LoY(), last_crop.HiX(), last_crop.HiY() ); + 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.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 ); + copy_image.Assign(*image); image = ©_image; image_copied = true; } - image->Crop( last_crop ); - } + image->Crop(last_crop); + } // end if difference in image vs displayed dimensions + last_scale = scale; last_zoom = zoom; last_x = x; last_y = y; return 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.Annotate(frame_text, image.centreCoord(frame_text)); - - 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 - { + /* double pts = */ vid_stream->EncodeFrame(image.Buffer(), image.Size()); + } 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("--ZoneMinderFrame\r\nContent-Type: image/jpeg\r\n", stdout); + 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); + fputs("\r\n\r\n", stdout); fflush(stdout); } - last_frame_sent = TV_2_FLOAT(now); + last_frame_sent = now; return true; } @@ -333,7 +369,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)); @@ -344,12 +380,12 @@ void StreamBase::openComms() { } snprintf(rem_sock_path, sizeof(rem_sock_path), "%s/zms-%06dw.sock", staticConfig.PATH_SOCKS.c_str(), connkey); - strncpy(rem_addr.sun_path, rem_sock_path, sizeof(rem_addr.sun_path)-1); + strncpy(rem_addr.sun_path, rem_sock_path, sizeof(rem_addr.sun_path)); rem_addr.sun_family = AF_UNIX; - gettimeofday(&last_comm_update, NULL); + last_comm_update = std::chrono::steady_clock::now(); + Debug(3, "comms open at %s", loc_sock_path); } // end if connKey > 0 - Debug(3, "comms open at %s", loc_sock_path); } // end void StreamBase::openComms() void StreamBase::closeComms() { diff --git a/src/zm_stream.h b/src/zm_stream.h index eb99b74c1..d89d97e7f 100644 --- a/src/zm_stream.h +++ b/src/zm_stream.h @@ -20,23 +20,31 @@ #ifndef ZM_STREAM_H #define ZM_STREAM_H -#include -#include - -#include "zm.h" +#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 { public: - typedef enum { STREAM_JPEG, STREAM_RAW, STREAM_ZIP, STREAM_SINGLE, STREAM_MPEG } StreamType; + typedef enum { + STREAM_JPEG, + STREAM_RAW, + STREAM_ZIP, + STREAM_SINGLE, + STREAM_MPEG + } StreamType; + typedef enum { FRAME_NORMAL, FRAME_ANALYSIS } FrameType; 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 }; @@ -56,126 +64,172 @@ protected: char msg_data[256]; } DataMsg; - typedef enum { MSG_CMD=1, MSG_DATA_WATCH, MSG_DATA_EVENT } MsgType; - typedef enum { CMD_NONE=0, CMD_PAUSE, CMD_PLAY, CMD_STOP, CMD_FASTFWD, CMD_SLOWFWD, CMD_SLOWREV, CMD_FASTREV, CMD_ZOOMIN, CMD_ZOOMOUT, CMD_PAN, CMD_SCALE, CMD_PREV, CMD_NEXT, CMD_SEEK, CMD_VARPLAY, CMD_GET_IMAGE, CMD_QUIT, CMD_QUERY=99 } MsgCommand; + typedef enum { + MSG_CMD=1, + MSG_DATA_WATCH, + MSG_DATA_EVENT + } MsgType; + + typedef enum { + CMD_NONE=0, + CMD_PAUSE, + CMD_PLAY, + CMD_STOP, + CMD_FASTFWD, + CMD_SLOWFWD, + CMD_SLOWREV, + CMD_FASTREV, + CMD_ZOOMIN, + CMD_ZOOMOUT, + CMD_PAN, + CMD_SCALE, + CMD_PREV, + CMD_NEXT, + CMD_SEEK, + CMD_VARPLAY, + CMD_GET_IMAGE, + CMD_QUIT, + CMD_MAXFPS, + CMD_ANALYZE_ON, + CMD_ANALYZE_OFF, + CMD_QUERY=99 + } MsgCommand; protected: - Monitor *monitor; + int monitor_id; + std::shared_ptr monitor; StreamType type; + FrameType frame_type; const char *format; int replay_rate; int scale; int last_scale; int zoom; int last_zoom; - double maxfps; int bitrate; unsigned short last_x, last_y; unsigned short x, y; - -protected: + bool send_analysis; + bool send_objdetect; int connkey; int sd; - char loc_sock_path[PATH_MAX]; + char loc_sock_path[108]; struct sockaddr_un loc_addr; - char rem_sock_path[PATH_MAX]; + char rem_sock_path[108]; struct sockaddr_un rem_addr; - char sock_path_lock[PATH_MAX]; + char sock_path_lock[108]; int lock_fd; - -protected: bool paused; int step; - struct timeval now; - struct timeval last_comm_update; + TimePoint now; + TimePoint 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 + TimePoint 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; + TimePoint last_frame_sent; + SystemTimePoint last_frame_timestamp; + TimePoint when_to_send_next_frame; // When to send next frame so if now < send_next_frame, skip -#if HAVE_LIBAVCODEC VideoStream *vid_stream; -#endif // HAVE_LIBAVCODEC CmdMsg msg; protected: - bool loadMonitor( int monitor_id ); + bool loadMonitor(int monitor_id); bool checkInitialised(); - void updateFrameRate( double fps ); - Image *prepareImage( Image *image ); - bool sendTextFrame( const char *text ); + void updateFrameRate(double fps); + Image *prepareImage(Image *image); bool checkCommandQueue(); - virtual void processCommand( const CmdMsg *msg )=0; + virtual void processCommand(const CmdMsg *msg)=0; public: - StreamBase() { - monitor = 0; + StreamBase(): + monitor_id(0), + monitor(nullptr), + type(DEFAULT_TYPE), + frame_type(FRAME_NORMAL), + format(""), + replay_rate(DEFAULT_RATE), + scale(DEFAULT_SCALE), + last_scale(DEFAULT_SCALE), + zoom(DEFAULT_ZOOM), + last_zoom(DEFAULT_ZOOM), + bitrate(DEFAULT_BITRATE), + last_x(0), + last_y(0), + x(0), + y(0), + send_analysis(false), + send_objdetect(false), + connkey(0), + sd(-1), + lock_fd(0), + paused(false), + 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)); + memset(&rem_sock_path, 0, sizeof(rem_sock_path)); + memset(&rem_addr, 0, sizeof(rem_addr)); + memset(&sock_path_lock, 0, sizeof(sock_path_lock)); - type = DEFAULT_TYPE; - format = ""; - replay_rate = DEFAULT_RATE; - last_scale = scale = DEFAULT_SCALE; - last_zoom = zoom = DEFAULT_ZOOM; - maxfps = DEFAULT_MAXFPS; - bitrate = DEFAULT_BITRATE; - - paused = false; - step = 0; - last_x = x = 0; - last_y = y = 0; - - connkey = 0; - sd = -1; - lock_fd = 0; - memset( &loc_sock_path, 0, sizeof(loc_sock_path) ); - memset( &loc_addr, 0, sizeof(loc_addr) ); - memset( &rem_sock_path, 0, sizeof(rem_sock_path) ); - 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 = (struct timeval){0}; + vid_stream = nullptr; msg = { 0, { 0 } }; } virtual ~StreamBase(); - void setStreamType( StreamType p_type ) { + void setStreamType(StreamType p_type) { type = p_type; +#if ! HAVE_ZLIB_H + if ( type == STREAM_ZIP ) { + Error("zlib is required for zipped images. Falling back to raw image"); + type = STREAM_RAW; + } +#endif } - void setStreamFormat( const char *p_format ) { + void setStreamFrameType(FrameType p_type) { + frame_type = p_type; + } + void setStreamFormat(const char *p_format) { format = p_format; } - void setStreamScale( int p_scale ) { + void setStreamScale(int p_scale) { scale = p_scale; - if ( ! scale ) + if ( !scale ) scale = DEFAULT_SCALE; } - void setStreamReplayRate( int p_rate ) { - Debug(2,"Setting replay_rate %d", p_rate); + void setStreamReplayRate(int p_rate) { + Debug(1, "Setting replay_rate %d", p_rate); replay_rate = p_rate; } - void setStreamMaxFPS( double p_maxfps ) { + void setStreamMaxFPS(double p_maxfps) { + Debug(1, "Setting max fps to %f", p_maxfps); maxfps = p_maxfps; } - void setStreamBitrate( int p_bitrate ) { + void setStreamBitrate(int p_bitrate) { bitrate = p_bitrate; } - void setStreamQueue( int p_connkey ) { + void setStreamQueue(int p_connkey) { connkey = p_connkey; } + bool sendTextFrame(const char *text); virtual void openComms(); virtual void closeComms(); virtual void runStream()=0; diff --git a/src/zm_swscale.cpp b/src/zm_swscale.cpp index 1e91029ad..68e7af4ef 100644 --- a/src/zm_swscale.cpp +++ b/src/zm_swscale.cpp @@ -15,39 +15,33 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "zm_ffmpeg.h" -#include "zm_image.h" -#include "zm_rgb.h" +*/ #include "zm_swscale.h" -#if HAVE_LIBSWSCALE && HAVE_LIBAVUTIL -SWScale::SWScale() : gotdefaults(false), swscale_ctx(NULL), input_avframe(NULL), output_avframe(NULL) { - Debug(4,"SWScale object created"); +#include "zm_image.h" +#include "zm_logger.h" +SWScale::SWScale() : + gotdefaults(false), + swscale_ctx(nullptr), + input_avframe(nullptr), + output_avframe(nullptr), + default_width(0), + default_height(0) +{ + 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 == NULL ) { + 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 == NULL ) { + if (!output_avframe) { Error("Failed allocating AVFrame for the output"); return false; } @@ -65,13 +59,17 @@ SWScale::~SWScale() { if ( swscale_ctx ) { sws_freeContext(swscale_ctx); - swscale_ctx = NULL; + swscale_ctx = nullptr; } - Debug(4,"SWScale object destroyed"); + Debug(4, "SWScale object destroyed"); } -int SWScale::SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height) { +int SWScale::SetDefaults( + enum _AVPIXELFORMAT in_pf, + enum _AVPIXELFORMAT out_pf, + unsigned int width, + unsigned int height) { /* Assign the defaults */ default_input_pf = in_pf; @@ -84,83 +82,25 @@ int SWScale::SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, return 0; } -int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height) { - /* Parameter checking */ - if(in_buffer == NULL || out_buffer == NULL) { - Error("NULL Input or output buffer"); - return -1; - } - // if(in_pf == 0 || out_pf == 0) { - // Error("Invalid input or output pixel formats"); - // return -2; - // } - if (!width || !height) { - Error("Invalid width or height"); - return -3; - } - -#if LIBSWSCALE_VERSION_CHECK(0, 8, 0, 8, 0) - /* Warn if the input or output pixelformat is not supported */ - if(!sws_isSupportedInput(in_pf)) { - Warning("swscale does not support the input format: %c%c%c%c",(in_pf)&0xff,((in_pf)&0xff),((in_pf>>16)&0xff),((in_pf>>24)&0xff)); - } - if(!sws_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 - - /* Check the buffer sizes */ -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - size_t insize = av_image_get_buffer_size(in_pf, width, height,1); -#else - size_t insize = avpicture_get_size(in_pf, width, height); -#endif - if(insize != in_buffer_size) { - Error("The input buffer size does not match the expected size for the input format. Required: %d Available: %d", insize, in_buffer_size); - return -4; - } -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - size_t outsize = av_image_get_buffer_size(out_pf, width, height,1); -#else - size_t outsize = avpicture_get_size(out_pf, width, height); -#endif - - if(outsize < out_buffer_size) { - Error("The output buffer is undersized for the output format. Required: %d Available: %d", outsize, out_buffer_size); - return -5; - } +int SWScale::Convert( + AVFrame *in_frame, + AVFrame *out_frame +) { + AVPixelFormat format = fix_deprecated_pix_fmt((AVPixelFormat)in_frame->format); /* Get the context */ - swscale_ctx = sws_getCachedContext( swscale_ctx, width, height, in_pf, width, height, out_pf, SWS_FAST_BILINEAR, NULL, NULL, NULL ); - if(swscale_ctx == NULL) { + swscale_ctx = sws_getCachedContext(swscale_ctx, + in_frame->width, in_frame->height, format, + out_frame->width, out_frame->height, (AVPixelFormat)out_frame->format, + SWS_FAST_BILINEAR, NULL, NULL, NULL); + if ( swscale_ctx == NULL ) { Error("Failed getting swscale context"); return -6; } - - /* Fill in the buffers */ -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - if (av_image_fill_arrays(input_avframe->data, input_avframe->linesize, - (uint8_t*) in_buffer, in_pf, width, height, 1) <= 0) { -#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, width, height, 1) <= 0) { -#else - if (avpicture_fill((AVPicture*) output_avframe, out_buffer, out_pf, width, - height) <= 0) { -#endif - Error("Failed filling output frame with output buffer"); - return -8; - } - /* Do the conversion */ - if(!sws_scale(swscale_ctx, input_avframe->data, input_avframe->linesize, 0, height, output_avframe->data, output_avframe->linesize ) ) { + if (!sws_scale(swscale_ctx, + in_frame->data, in_frame->linesize, 0, in_frame->height, + out_frame->data, out_frame->linesize)) { Error("swscale conversion failed"); return -10; } @@ -168,23 +108,149 @@ int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint return 0; } -int SWScale::Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height) { - if(img->Width() != width) { - Error("Source image width differs. Source: %d Output: %d",img->Width(), width); +int SWScale::Convert( + const uint8_t* in_buffer, + const size_t in_buffer_size, + uint8_t* out_buffer, + const size_t out_buffer_size, + enum _AVPIXELFORMAT in_pf, + enum _AVPIXELFORMAT out_pf, + unsigned int width, + unsigned int height, + unsigned int new_width, + unsigned int new_height + ) { + Debug(1, "Convert: in_buffer %p in_buffer_size %zu out_buffer %p size %zu width %d height %d width %d height %d %d %d", + in_buffer, in_buffer_size, out_buffer, out_buffer_size, width, height, new_width, new_height, + in_pf, out_pf); + /* Parameter checking */ + if (in_buffer == nullptr) { + Error("NULL Input buffer"); + return -1; + } + if (out_buffer == nullptr) { + Error("NULL output buffer"); + return -1; + } + // if(in_pf == 0 || out_pf == 0) { + // Error("Invalid input or output pixel formats"); + // return -2; + // } + if (!width || !height || !new_height || !new_width) { + Error("Invalid width or height"); + return -3; + } + + in_pf = fix_deprecated_pix_fmt(in_pf); + + /* Warn if the input or output pixelformat is not supported */ + if (!sws_isSupportedInput(in_pf)) { + Warning("swscale does not support the input format: %c%c%c%c", + (in_pf)&0xff,((in_pf)&0xff),((in_pf>>16)&0xff),((in_pf>>24)&0xff)); + } + if (!sws_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)); + } + + 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) { + 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: %zu Available: %zu", + needed_outsize, + out_buffer_size); + return -5; + } + + /* Get the context */ + swscale_ctx = sws_getCachedContext(swscale_ctx, + width, height, in_pf, + new_width, new_height, out_pf, + SWS_FAST_BILINEAR, nullptr, nullptr, nullptr); + if (swscale_ctx == nullptr) { + 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 (av_image_fill_arrays(input_avframe->data, input_avframe->linesize, + (uint8_t*) in_buffer, in_pf, width, height, alignment) <= 0) { + Error("Failed filling input frame with input buffer"); + return -7; + } + if (av_image_fill_arrays(output_avframe->data, output_avframe->linesize, + out_buffer, out_pf, new_width, new_height, alignment) <= 0) { + Error("Failed filling output frame with output buffer"); + return -8; + } + + /* Do the conversion */ + if ( !sws_scale(swscale_ctx, + input_avframe->data, input_avframe->linesize, + 0, height, + output_avframe->data, output_avframe->linesize) ) { + Error("swscale conversion failed"); + return -10; + } + + return 0; +} + +int SWScale::Convert( + const uint8_t* in_buffer, + const size_t in_buffer_size, + uint8_t* out_buffer, + const size_t out_buffer_size, + enum _AVPIXELFORMAT in_pf, + enum _AVPIXELFORMAT out_pf, + unsigned int width, + unsigned int height) { + return Convert(in_buffer, in_buffer_size, out_buffer, out_buffer_size, in_pf, out_pf, width, height, width, height); +} + +int SWScale::Convert( + const Image* img, + uint8_t* out_buffer, + const size_t out_buffer_size, + enum _AVPIXELFORMAT in_pf, + enum _AVPIXELFORMAT out_pf, + unsigned int width, + unsigned int height) { + if ( img->Width() != width ) { + Error("Source image width differs. Source: %d Output: %d", img->Width(), width); return -12; } - if(img->Height() != height) { - Error("Source image height differs. Source: %d Output: %d",img->Height(), height); + if ( img->Height() != height ) { + Error("Source image height differs. Source: %d Output: %d", img->Height(), height); return -13; } - return Convert(img->Buffer(),img->Size(),out_buffer,out_buffer_size,in_pf,out_pf,width,height); + return Convert(img->Buffer(), img->Size(), out_buffer, out_buffer_size, in_pf, out_pf, width, height); } int SWScale::ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size) { - if(!gotdefaults) { + if ( !gotdefaults ) { Error("Defaults are not set"); return -24; } @@ -194,11 +260,14 @@ int SWScale::ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t int SWScale::ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size) { - if(!gotdefaults) { + if ( !gotdefaults ) { Error("Defaults are not set"); return -24; } return Convert(in_buffer,in_buffer_size,out_buffer,out_buffer_size,default_input_pf,default_output_pf,default_width,default_height); } -#endif // HAVE_LIBSWSCALE && HAVE_LIBAVUTIL + +size_t SWScale::GetBufferSize(enum _AVPIXELFORMAT pf, unsigned int width, unsigned int height) { + return av_image_get_buffer_size(pf, width, height, 1); +} diff --git a/src/zm_swscale.h b/src/zm_swscale.h index 62f99e342..c0d1e7120 100644 --- a/src/zm_swscale.h +++ b/src/zm_swscale.h @@ -1,22 +1,26 @@ #ifndef ZM_SWSCALE_H #define ZM_SWSCALE_H -#include "zm_image.h" +#include "zm_config.h" #include "zm_ffmpeg.h" +class Image; + /* SWScale wrapper class to make our life easier and reduce code reuse */ -#if HAVE_LIBSWSCALE && HAVE_LIBAVUTIL class SWScale { public: SWScale(); ~SWScale(); - bool init(); + bool init(); int SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); int ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size); int ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size); + int Convert( AVFrame *in_frame, AVFrame *out_frame ); int Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); - + int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height, unsigned int new_width, unsigned int new_height); + static size_t GetBufferSize(enum _AVPIXELFORMAT in_pf, unsigned int width, unsigned int height); + protected: bool gotdefaults; struct SwsContext* swscale_ctx; @@ -27,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 03d048bea..000000000 --- a/src/zm_thread.cpp +++ /dev/null @@ -1,308 +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, 0); - 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, 0 ); - 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, NULL) < 0 ) - Error("Unable to create pthread mutex: %s", strerror(errno)); -} - -Mutex::~Mutex() { - if ( locked() ) - Warning("Destroying mutex when locked"); - if ( pthread_mutex_destroy(&mMutex) < 0 ) - Error("Unable to destroy pthread mutex: %s", strerror(errno)); -} - -int Mutex::trylock() { - return pthread_mutex_trylock(&mMutex); -} -void Mutex::lock() { - if ( pthread_mutex_lock(&mMutex) < 0 ) - throw ThreadException( stringtf( "Unable to lock pthread mutex: %s", strerror(errno) ) ); -} - -void Mutex::lock( int secs ) { - struct timespec timeout = getTimeout( secs ); - if ( pthread_mutex_timedlock( &mMutex, &timeout ) < 0 ) - throw ThreadException( stringtf( "Unable to timedlock pthread mutex: %s", strerror(errno) ) ); -} - -void Mutex::lock( double secs ) { - struct timespec timeout = getTimeout( secs ); - if ( pthread_mutex_timedlock( &mMutex, &timeout ) < 0 ) - throw ThreadException( stringtf( "Unable to timedlock pthread mutex: %s", strerror(errno) ) ); -} - -void Mutex::unlock() { - if ( pthread_mutex_unlock( &mMutex ) < 0 ) - throw ThreadException( stringtf( "Unable to unlock pthread mutex: %s", strerror(errno) ) ); -} - -bool Mutex::locked() { - int state = pthread_mutex_trylock( &mMutex ); - if ( state != 0 && state != EBUSY ) - throw ThreadException( stringtf( "Unable to trylock pthread mutex: %s", strerror(errno) ) ); - if ( state != EBUSY ) - unlock(); - return( state == EBUSY ); -} - -RecursiveMutex::RecursiveMutex() { - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - - if ( pthread_mutex_init(&mMutex, &attr) < 0 ) - Error("Unable to create pthread mutex: %s", strerror(errno)); -} - -Condition::Condition( Mutex &mutex ) : mMutex( mutex ) { - if ( pthread_cond_init( &mCondition, NULL ) < 0 ) - throw ThreadException( stringtf( "Unable to create pthread condition: %s", strerror(errno) ) ); -} - -Condition::~Condition() { - if ( pthread_cond_destroy( &mCondition ) < 0 ) - Error("Unable to destroy pthread condition: %s", strerror(errno)); -} - -void Condition::wait() { - // Locking done outside of this function - if ( pthread_cond_wait( &mCondition, mMutex.getMutex() ) < 0 ) - throw ThreadException( stringtf( "Unable to wait pthread condition: %s", strerror(errno) ) ); -} - -bool Condition::wait( int secs ) { - // Locking done outside of this function - Debug( 8, "Waiting for %d seconds", secs ); - struct timespec timeout = getTimeout( secs ); - if ( pthread_cond_timedwait( &mCondition, mMutex.getMutex(), &timeout ) < 0 && errno != ETIMEDOUT ) - throw ThreadException( stringtf( "Unable to timedwait pthread condition: %s", strerror(errno) ) ); - return( errno != ETIMEDOUT ); -} - -bool Condition::wait( double secs ) { - // Locking done outside of this function - struct timespec timeout = getTimeout( secs ); - if ( pthread_cond_timedwait( &mCondition, mMutex.getMutex(), &timeout ) < 0 && errno != ETIMEDOUT ) - throw ThreadException( stringtf( "Unable to timedwait pthread condition: %s", strerror(errno) ) ); - return( errno != ETIMEDOUT ); -} - -void Condition::signal() { - if ( pthread_cond_signal( &mCondition ) < 0 ) - throw ThreadException( stringtf( "Unable to signal pthread condition: %s", strerror(errno) ) ); -} - -void Condition::broadcast() { - if ( pthread_cond_broadcast( &mCondition ) < 0 ) - throw ThreadException( stringtf( "Unable to broadcast pthread condition: %s", strerror(errno) ) ); -} - -template const T ThreadData::getValue() const { - mMutex.lock(); - const T valueCopy = mValue; - mMutex.unlock(); - return valueCopy; -} - -template T ThreadData::setValue(const T value) { - mMutex.lock(); - const T valueCopy = mValue = value; - mMutex.unlock(); - return valueCopy; -} - -template const T ThreadData::getUpdatedValue() const { - Debug(8, "Waiting for value update, %p", this); - mMutex.lock(); - mChanged = false; - mCondition.wait(); - const T valueCopy = mValue; - mMutex.unlock(); - Debug(9, "Got value update, %p", this); - return valueCopy; -} - -template const T ThreadData::getUpdatedValue(double secs) const { - Debug(8, "Waiting for value update, %.2f secs, %p", secs, this); - mMutex.lock(); - mChanged = false; - //do { - mCondition.wait( secs ); - //} while ( !mChanged ); - const T valueCopy = mValue; - mMutex.unlock(); - Debug(9, "Got value update, %p", this ); - return valueCopy; -} - -template const T ThreadData::getUpdatedValue(int secs) const { - Debug(8, "Waiting for value update, %d secs, %p", secs, this); - mMutex.lock(); - mChanged = false; - //do { - mCondition.wait(secs); - //} while ( !mChanged ); - const T valueCopy = mValue; - mMutex.unlock(); - Debug(9, "Got value update, %p", this); - return valueCopy; -} - -template void ThreadData::updateValueSignal(const T value) { - Debug(8, "Updating value with signal, %p", this); - mMutex.lock(); - mValue = value; - mChanged = true; - mCondition.signal(); - mMutex.unlock(); - Debug(9, "Updated value, %p", this); -} - -template void ThreadData::updateValueBroadcast( const T value ) { - Debug(8, "Updating value with broadcast, %p", this); - mMutex.lock(); - mValue = value; - mChanged = true; - mCondition.broadcast(); - mMutex.unlock(); - Debug(9, "Updated value, %p", this); -} - -Thread::Thread() : - mThreadCondition( mThreadMutex ), - mPid( -1 ), - mStarted( false ), - mRunning( false ) -{ - Debug( 1, "Creating thread" ); -} - -Thread::~Thread() { - Debug( 1, "Destroying thread %d", mPid ); - if ( mStarted ) - join(); -} - -void *Thread::mThreadFunc( void *arg ) { - Debug( 2, "Invoking thread" ); - - Thread *thisPtr = (Thread *)arg; - thisPtr->status = 0; - try { - thisPtr->mThreadMutex.lock(); - thisPtr->mPid = thisPtr->id(); - thisPtr->mThreadCondition.signal(); - thisPtr->mThreadMutex.unlock(); - thisPtr->mRunning = true; - thisPtr->status = thisPtr->run(); - thisPtr->mRunning = false; - Debug( 2, "Exiting thread, status %p", (void *)&(thisPtr->status) ); - return (void *)&(thisPtr->status); - } catch ( const ThreadException &e ) { - Error( "%s", e.getMessage().c_str() ); - thisPtr->mRunning = false; - Debug( 2, "Exiting thread after exception, status %p", (void *)-1 ); - return (void *)-1; - } -} - -void Thread::start() { - Debug( 1, "Starting thread" ); - if ( isThread() ) - throw ThreadException( "Can't self start thread" ); - mThreadMutex.lock(); - if ( !mStarted ) { - pthread_attr_t threadAttrs; - pthread_attr_init( &threadAttrs ); - pthread_attr_setscope( &threadAttrs, PTHREAD_SCOPE_SYSTEM ); - - mStarted = true; - if ( pthread_create( &mThread, &threadAttrs, mThreadFunc, this ) < 0 ) - throw ThreadException( stringtf( "Can't create thread: %s", strerror(errno) ) ); - pthread_attr_destroy( &threadAttrs ); - } else { - Error( "Attempt to start already running thread %d", mPid ); - } - mThreadCondition.wait(); - mThreadMutex.unlock(); - Debug( 1, "Started thread %d", mPid ); -} - -void Thread::join() { - Debug( 1, "Joining thread %d", mPid ); - if ( isThread() ) - throw ThreadException( "Can't self join thread" ); - mThreadMutex.lock(); - if ( mPid >= 0 ) { - if ( mStarted ) { - void *threadStatus = 0; - if ( pthread_join( mThread, &threadStatus ) < 0 ) - throw ThreadException( stringtf( "Can't join sender thread: %s", strerror(errno) ) ); - mStarted = false; - Debug( 1, "Thread %d exited, status %p", mPid, threadStatus ); - } else { - Warning( "Attempt to join already finished thread %d", mPid ); - } - } else { - Warning( "Attempt to join non-started thread %d", mPid ); - } - mThreadMutex.unlock(); - Debug( 1, "Joined thread %d", mPid ); -} - -void Thread::kill( int signal ) { - pthread_kill( mThread, signal ); -} - -// Some explicit template instantiations -#include "zm_threaddata.cpp" diff --git a/src/zm_thread.h b/src/zm_thread.h deleted file mode 100644 index 8cdfb892c..000000000 --- a/src/zm_thread.h +++ /dev/null @@ -1,269 +0,0 @@ -// -// ZoneMinder Thread Class Interface, $Date$, $Revision$ -// Copyright (C) 2001-2008 Philip Coombes -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// - -#ifndef ZM_THREAD_H -#define ZM_THREAD_H - -class RecursiveMutex; - - -#include "zm_config.h" -#include -#include -#ifdef HAVE_SYS_SYSCALL_H -#include -#endif // HAVE_SYS_SYSCALL_H -#include "zm_exception.h" -#include "zm_utils.h" -#ifdef __FreeBSD__ -#include -#endif - -class ThreadException : public Exception { -private: -#ifndef SOLARIS - pid_t pid() { - pid_t tid; -#ifdef __FreeBSD__ - long lwpid; - thr_self(&lwpid); - tid = lwpid; -#else - #ifdef __FreeBSD_kernel__ - if ( (syscall(SYS_thr_self, &tid)) < 0 ) // Thread/Process id - # else - tid=syscall(SYS_gettid); - #endif -#endif - return tid; - } -#else - pthread_t pid() { return( pthread_self() ); } -#endif -public: - explicit ThreadException( const std::string &message ) : Exception( stringtf("(%d) ", (long int)pid())+message ) { - } -}; - -class Mutex { - friend class Condition; - - private: - pthread_mutex_t mMutex; - - public: - Mutex(); - ~Mutex(); - - private: - pthread_mutex_t *getMutex() { - return &mMutex; - } - - public: - int trylock(); - void lock(); - void lock( int secs ); - void lock( double secs ); - void unlock(); - bool locked(); -}; - -class RecursiveMutex : public Mutex { - private: - pthread_mutex_t mMutex; - public: - RecursiveMutex(); -}; - -class ScopedMutex { -private: - Mutex &mMutex; - -public: - explicit ScopedMutex( Mutex &mutex ) : mMutex( mutex ) { - mMutex.lock(); - } - ~ScopedMutex() { - mMutex.unlock(); - } - -private: - ScopedMutex( const ScopedMutex & ); -}; - -class Condition { -private: - Mutex &mMutex; - pthread_cond_t mCondition; - -public: - explicit Condition(Mutex &mutex); - ~Condition(); - - void wait(); - bool wait(int secs); - bool wait(double secs); - void signal(); - void broadcast(); -}; - -class Semaphore : public Condition { -private: - Mutex mMutex; - -public: - Semaphore() : Condition(mMutex) { - } - - void wait() { - mMutex.lock(); - Condition::wait(); - mMutex.unlock(); - } - bool wait(int secs) { - mMutex.lock(); - bool result = Condition::wait(secs); - mMutex.unlock(); - return result; - } - bool wait(double secs) { - mMutex.lock(); - bool result = Condition::wait(secs); - mMutex.unlock(); - return result; - } - void signal() { - mMutex.lock(); - Condition::signal(); - mMutex.unlock(); - } - void broadcast() { - mMutex.lock(); - Condition::broadcast(); - mMutex.unlock(); - } -}; - -template class ThreadData { -private: - T mValue; - mutable bool mChanged; - mutable Mutex mMutex; - mutable Condition mCondition; - -public: - __attribute__((used)) ThreadData() : mValue(0), mCondition( mMutex ) { - mChanged = false; - } - explicit __attribute__((used)) ThreadData( T value ) : mValue( value ), mCondition( mMutex ) { - mChanged = false; - } - //~ThreadData() {} - - __attribute__((used)) operator T() const - { - return( getValue() ); - } - __attribute__((used)) const T operator=( const T value ) - { - return( setValue( value ) ); - } - - __attribute__((used)) const T getValueImmediate() const - { - return( mValue ); - } - __attribute__((used)) T setValueImmediate( const T value ) - { - return( mValue = value ); - } - __attribute__((used)) const T getValue() const; - __attribute__((used)) T setValue( const T value ); - __attribute__((used)) const T getUpdatedValue() const; - __attribute__((used)) const T getUpdatedValue( double secs ) const; - __attribute__((used)) const T getUpdatedValue( int secs ) const; - __attribute__((used)) void updateValueSignal( const T value ); - __attribute__((used)) void updateValueBroadcast( const T value ); -}; - -class Thread { -public: - typedef void *(*ThreadFunc)( void * ); - -protected: - pthread_t mThread; - - Mutex mThreadMutex; - Condition mThreadCondition; -#ifndef SOLARIS - pid_t mPid; -#else - pthread_t mPid; -#endif - bool mStarted; - bool mRunning; - int status; // Used in various functions to get around return a local variable - -protected: - Thread(); - virtual ~Thread(); - -#ifndef SOLARIS - pid_t id() const { - pid_t tid; -#ifdef __FreeBSD__ - long lwpid; - thr_self(&lwpid); - tid = lwpid; -#else - #ifdef __FreeBSD_kernel__ - if ( (syscall(SYS_thr_self, &tid)) < 0 ) // Thread/Process id - - #else - tid=syscall(SYS_gettid); - #endif -#endif - return tid; - } -#else - pthread_t id() const { - return pthread_self(); - } -#endif - void exit( int p_status = 0 ) { - //INFO( "Exiting" ); - pthread_exit( (void *)&p_status ); - } - static void *mThreadFunc( void *arg ); - -public: - virtual int run() = 0; - - void start(); - void join(); - void kill( int signal ); - bool isThread() { - return( mPid > -1 && pthread_equal( pthread_self(), mThread ) ); - } - bool isStarted() const { return mStarted; } - bool isRunning() const { return mRunning; } -}; - -#endif // ZM_THREAD_H diff --git a/src/zm_time.cpp b/src/zm_time.cpp index 417ee2b2b..041962003 100644 --- a/src/zm_time.cpp +++ b/src/zm_time.cpp @@ -1,5 +1,5 @@ // -// ZoneMinder Time Functions & Definitions Implementation, $Date$, $Revision$ +// ZoneMinder Time Functions & Definitions, $Date$, $Revision$ // Copyright (C) 2001-2008 Philip Coombes // // This program is free software; you can redistribute it and/or @@ -19,4 +19,34 @@ #include "zm_time.h" -// Blank +#include + +std::string SystemTimePointToString(SystemTimePoint tp) { + time_t tp_sec = std::chrono::system_clock::to_time_t(tp); + Microseconds now_frac = std::chrono::duration_cast( + tp.time_since_epoch() - std::chrono::duration_cast(tp.time_since_epoch())); + + std::string timeString; + timeString.reserve(64); + char *timePtr = &*(timeString.begin()); + tm tp_tm = {}; + timePtr += strftime(timePtr, timeString.capacity(), "%x %H:%M:%S", localtime_r(&tp_sec, &tp_tm)); + snprintf(timePtr, timeString.capacity() - (timePtr - timeString.data()), ".%06" PRIi64, static_cast(now_frac.count())); + return timeString; +} + +std::string TimePointToString(TimePoint tp) { + time_t tp_sec = std::chrono::system_clock::to_time_t( + std::chrono::system_clock::now() + (tp - std::chrono::steady_clock::now())); + + Microseconds now_frac = std::chrono::duration_cast( + tp.time_since_epoch() - std::chrono::duration_cast(tp.time_since_epoch())); + + std::string timeString; + timeString.reserve(64); + char *timePtr = &*(timeString.begin()); + tm tp_tm = {}; + timePtr += strftime(timePtr, timeString.capacity(), "%x %H:%M:%S", localtime_r(&tp_sec, &tp_tm)); + snprintf(timePtr, timeString.capacity() - (timePtr - timeString.data()), ".%06" PRIi64, static_cast(now_frac.count())); + return timeString; +} diff --git a/src/zm_time.h b/src/zm_time.h index a9af32777..d9d131944 100644 --- a/src/zm_time.h +++ b/src/zm_time.h @@ -20,188 +20,108 @@ #ifndef ZM_TIME_H #define ZM_TIME_H -#include "zm.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, NULL ); - 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, NULL ); - 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, NULL ); - 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, NULL ); - 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: + explicit 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_; +}; + +std::string SystemTimePointToString(SystemTimePoint tp); +std::string TimePointToString(TimePoint tp); #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 da3b95783..000000000 --- a/src/zm_timer.h +++ /dev/null @@ -1,109 +0,0 @@ -// -// ZoneMinder Timer Class Interface, $Date$, $Revision$ -// Copyright (C) 2001-2008 Philip Coombes -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// - -#ifndef ZM_TIMER_H -#define ZM_TIMER_H - -#ifdef HAVE_SYS_SYSCALL_H -#include -#endif // HAVE_SYS_SYSCALL_H -#include "zm_thread.h" - -#include "zm_exception.h" - -class Timer -{ -private: - class TimerException : public Exception - { - private: -#ifndef SOLARIS - pid_t pid() { - pid_t tid; -#ifdef __FreeBSD__ - long lwpid; - thr_self(&lwpid); - tid = lwpid; -#else - #ifdef __FreeBSD_kernel__ - if ( (syscall(SYS_thr_self, &tid)) < 0 ) // Thread/Process id - #else - tid=syscall(SYS_gettid); - #endif -#endif - return tid; - } -#else - pthread_t pid() { return( pthread_self() ); } -#endif - public: - explicit TimerException( const std::string &message ) : Exception( stringtf("(%d) ", (long int)pid())+message ) { - } - }; - - class TimerThread : public Thread - { - private: - typedef ThreadData ExpiryFlag; - - private: - static int mNextTimerId; - - private: - int mTimerId; - Timer &mTimer; - int mDuration; - int mRepeat; - int mReset; - ExpiryFlag mExpiryFlag; - Mutex mAccessMutex; - - private: - void quit() - { - cancel(); - } - - public: - TimerThread( Timer &timer, int timeout, bool repeat ); - ~TimerThread(); - - void cancel(); - void reset(); - int run(); - }; - -protected: - TimerThread mTimerThread; - -protected: - Timer( int timeout, bool repeat=false ); - -public: - virtual ~Timer(); - -protected: - virtual void expire()=0; - -public: - void cancel(); - void reset(); -}; - -#endif // ZM_TIMER_H diff --git a/src/zm_user.cpp b/src/zm_user.cpp index 1ebd3f1ff..44a22df38 100644 --- a/src/zm_user.cpp +++ b/src/zm_user.cpp @@ -1,47 +1,29 @@ /* * ZoneMinder regular expression 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.h" -#include "zm_db.h" +*/ #include "zm_user.h" -#include -#include -#include -#include - -#if HAVE_GNUTLS_OPENSSL_H -#include -#endif -#if HAVE_GNUTLS_GNUTLS_H -#include -#endif - -#if HAVE_GCRYPT_H -#include -#elif HAVE_LIBCRYPTO -#include -#endif // HAVE_L || HAVE_LIBCRYPTO - -#include "zm_utils.h" #include "zm_crypt.h" +#include "zm_logger.h" +#include "zm_time.h" +#include "zm_utils.h" +#include User::User() { id = 0; @@ -55,7 +37,7 @@ User::User(const MYSQL_ROW &dbrow) { id = atoi(dbrow[index++]); strncpy(username, dbrow[index++], sizeof(username)-1); strncpy(password, dbrow[index++], sizeof(password)-1); - enabled = (bool)atoi(dbrow[index++]); + enabled = static_cast(atoi(dbrow[index++])); stream = (Permission)atoi(dbrow[index++]); events = (Permission)atoi(dbrow[index++]); control = (Permission)atoi(dbrow[index++]); @@ -63,8 +45,8 @@ 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, ","); - for( StringVector::iterator i = ids.begin(); i < ids.end(); ++i ) { + StringVector ids = Split(monitor_ids_str, ","); + for ( StringVector::iterator i = ids.begin(); i < ids.end(); ++i ) { monitor_ids.push_back(atoi((*i).c_str())); } } @@ -75,9 +57,9 @@ User::~User() { } void User::Copy(const User &u) { - id=u.id; - strncpy(username, u.username, sizeof(username)-1); - strncpy(password, u.password, sizeof(password)-1); + id = u.id; + strncpy(username, u.username, sizeof(username)); + strncpy(password, u.password, sizeof(password)); enabled = u.enabled; stream = u.stream; events = u.events; @@ -90,8 +72,9 @@ void User::Copy(const User &u) { bool User::canAccess(int monitor_id) { if ( monitor_ids.empty() ) return true; - - for ( std::vector::iterator i = monitor_ids.begin(); i != monitor_ids.end(); ++i ) { + + for ( std::vector::iterator i = monitor_ids.begin(); + i != monitor_ids.end(); ++i ) { if ( *i == monitor_id ) { return true; } @@ -102,237 +85,185 @@ 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); + MYSQL_RES *result = zmDbFetch(sql); + if (!result) + return nullptr; - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } - delete safer_username; + if ( mysql_num_rows(result) == 1 ) { + MYSQL_ROW dbrow = mysql_fetch_row(result); + User *user = new User(dbrow); - 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 ) { - mysql_free_result(result); - Warning("Unable to authenticate user %s", username); - return NULL; - } - - MYSQL_ROW dbrow = mysql_fetch_row(result); - User *user = new User(dbrow); + if ( + (! password ) // relay type must be none + || + verifyPassword(username, password, user->getPassword()) ) { + mysql_free_result(result); + Info("Authenticated user '%s'", user->getUsername()); + return user; + } + } // end if 1 result from db mysql_free_result(result); - if ( !password ) { - // relay type must be none - return user; - } - - if ( verifyPassword(username, password, user->getPassword()) ) { - Info("Authenticated user '%s'", user->getUsername()); - return user; - } - Warning("Unable to authenticate user %s", username); - return NULL; -} + 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 = ""; - - if (use_remote_addr) { - remote_addr = std::string(getenv( "REMOTE_ADDR" )); + + if ( use_remote_addr ) { + remote_addr = std::string(getenv("REMOTE_ADDR")); if ( remote_addr == "" ) { - Warning( "Can't determine remote address, using null" ); + Warning("Can't determine remote address, using null"); remote_addr = ""; } key += remote_addr; } - Debug (1,"Inside zmLoadTokenUser, formed key=%s", key.c_str()); + Debug(1, "Inside zmLoadTokenUser, formed key=%s", key.c_str()); std::pair ans = verifyToken(jwt_token_str, key); std::string username = ans.first; unsigned int iat = ans.second; - Debug (1,"retrieved user '%s' from token", username.c_str()); + Debug(1, "retrieved user '%s' from token", username.c_str()); - if (username != "") { - 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() ); + if ( username == "" ) { + return nullptr; + } - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } + std::string sql = stringtf("SELECT `Id`, `Username`, `Password`, `Enabled`, `Stream`+0, `Events`+0," + " `Control`+0, `Monitors`+0, `System`+0, `MonitorIds`, `TokenMinExpiry`" + " FROM `Users` WHERE `Username` = '%s' AND `Enabled` = 1", username.c_str()); - MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } - int n_users = mysql_num_rows(result); + MYSQL_RES *result = zmDbFetch(sql); + if (!result) + return nullptr; - if ( n_users != 1 ) { - mysql_free_result(result); - Error("Unable to authenticate user '%s'", username.c_str()); - return NULL; - } - - MYSQL_ROW dbrow = mysql_fetch_row(result); - User *user = new User(dbrow); - unsigned int stored_iat = strtoul(dbrow[10], NULL,0 ); - - if (stored_iat > iat ) { // admin revoked tokens - mysql_free_result(result); - Error("Token was revoked for '%s'", username.c_str()); - return NULL; - } - - Debug (1,"Got stored expiry time of %u",stored_iat); - Debug (1,"Authenticated user '%s' via token", username.c_str()); + int n_users = mysql_num_rows(result); + if ( n_users != 1 ) { mysql_free_result(result); - return user; - - } - else { - return NULL; + Error("Unable to authenticate user '%s'", username.c_str()); + return nullptr; } -} + MYSQL_ROW dbrow = mysql_fetch_row(result); + User *user = new User(dbrow); + unsigned int stored_iat = strtoul(dbrow[10], nullptr, 0); + + if ( stored_iat > iat ) { // admin revoked tokens + mysql_free_result(result); + Error("Token was revoked for '%s'", username.c_str()); + return nullptr; + } + + Debug(1, "Authenticated user '%s' via token with last revoke time: %u", + username.c_str(), stored_iat); + mysql_free_result(result); + return user; +} // 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 - +User *zmLoadAuthUser(const char *auth, bool use_remote_addr) { const char *remote_addr = ""; - if ( use_remote_addr ) { - remote_addr = getenv( "REMOTE_ADDR" ); - if ( !remote_addr ) { - Warning( "Can't determine remote address, using null" ); + if (use_remote_addr) { + remote_addr = getenv("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'", auth ); - char sql[ZM_SQL_SML_BUFSIZ] = ""; - snprintf( sql, sizeof(sql), "SELECT `Id`, `Username`, `Password`, `Enabled`, `Stream`+0, `Events`+0, `Control`+0, `Monitors`+0, `System`+0, `MonitorIds` FROM `Users` WHERE `Enabled` = 1" ); + Debug(1, "Attempting to authenticate user from auth string '%s', remote addr(%s)", auth, remote_addr); + std::string sql = "SELECT `Id`, `Username`, `Password`, `Enabled`," + " `Stream`+0, `Events`+0, `Control`+0, `Monitors`+0, `System`+0," + " `MonitorIds` FROM `Users` WHERE `Enabled` = 1"; - if ( mysql_query( &dbconn, sql ) ) { - Error( "Can't run query: %s", mysql_error( &dbconn ) ); - exit( mysql_errno( &dbconn ) ); + MYSQL_RES *result = zmDbFetch(sql); + if (!result) + return nullptr; + + int n_users = mysql_num_rows(result); + if (n_users < 1) { + mysql_free_result(result); + Warning("Unable to authenticate user"); + return nullptr; } - MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) { - Error( "Can't use query result: %s", mysql_error( &dbconn ) ); - exit( mysql_errno( &dbconn ) ); - } - int n_users = mysql_num_rows( result ); + SystemTimePoint now = std::chrono::system_clock::now(); + Hours hours = Hours(config.auth_hash_ttl); - if ( n_users < 1 ) { - mysql_free_result( result ); - Warning( "Unable to authenticate user" ); - return( 0 ); + if (hours == Hours(0)) { + Warning("No value set for ZM_AUTH_HASH_TTL. Defaulting to 2."); + hours = Hours(2); + } else { + 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())); } - 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]; - char auth_key[512] = ""; - char auth_md5[32+1] = ""; - size_t md5len = 16; - unsigned char md5sum[md5len]; + 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); - time_t now = time( 0 ); - unsigned int hours = config.auth_hash_ttl; + 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 ( ! hours ) { - Warning("No value set for ZM_AUTH_HASH_TTL. Defaulting to 2."); - hours = 2; - } else { - Debug( 1, "AUTH_HASH_TTL is %d", hours ); - } + zm::crypto::MD5::Digest md5_digest = zm::crypto::MD5::GetDigestOf(auth_key); + std::string auth_md5 = ByteArrayToHexString(md5_digest); - for ( unsigned int i = 0; i < hours; i++, now -= 3600 ) { - struct tm *now_tm = localtime( &now ); + Debug(1, "Checking auth_key '%s' -> auth_md5 '%s' == '%s'", auth_key.c_str(), auth_md5.c_str(), auth); - snprintf( auth_key, sizeof(auth_key), "%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 - ); - -#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, strlen(auth_key) }; - gnutls_fingerprint( GNUTLS_DIG_MD5, &md5data, md5sum, &md5len ); -#endif - auth_md5[0] = '\0'; - for ( unsigned int j = 0; j < md5len; j++ ) { - sprintf( &auth_md5[2*j], "%02x", md5sum[j] ); - } - 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() ); - mysql_free_result( result ); - return( user ); + User *user = new User(dbrow); + Debug(1, "Authenticated user '%s'", user->getUsername()); + mysql_free_result(result); + return user; } else { - Debug(1, "No match for %s", auth ); + Debug(1, "No match for %s", auth); } - } - } - mysql_free_result( result ); -#else // HAVE_DECL_MD5 - Error( "You need to build with gnutls or openssl installed to use hash based authentication" ); -#endif // HAVE_DECL_MD5 - Debug(1, "No user found for auth_key %s", auth ); - return 0; -} + } // end foreach hour + } // end foreach user + mysql_free_result(result); -//Function to check Username length -bool checkUser ( const char *username) { - if ( ! username ) + Debug(1, "No user found for auth_key %s", auth); + return nullptr; +} // end User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) + +// Function to check Username length +bool checkUser(const char *username) { + if ( !username ) return false; if ( strlen(username) > 32 ) return false; return true; } -//Function to check password length -bool checkPass (const char *password) { + +// Function to check password length +bool checkPass(const char *password) { if ( !password ) return false; if ( strlen(password) > 64 ) diff --git a/src/zm_user.h b/src/zm_user.h index 42fe07554..a8b78b488 100644 --- a/src/zm_user.h +++ b/src/zm_user.h @@ -17,12 +17,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "zm.h" -#include "zm_db.h" + #ifndef ZM_USER_H #define ZM_USER_H +#include "zm_db.h" #include #include @@ -46,13 +46,13 @@ class User { User(); explicit User(const MYSQL_ROW &dbrow); ~User(); - User(User &u) { Copy(u); } + User(const User &u) { Copy(u); } void Copy(const User &u); User& operator=(const User &u) { Copy(u); return *this; } - const int Id() const { return id; } + int Id() const { return id; } const char *getUsername() const { return username; } const char *getPassword() const { return password; } bool isEnabled() const { return enabled; } @@ -66,7 +66,7 @@ class User { User *zmLoadUser(const char *username, const char *password=0); User *zmLoadAuthUser(const char *auth, bool use_remote_addr); -User *zmLoadTokenUser(std::string jwt, bool use_remote_addr); +User *zmLoadTokenUser(const std::string &jwt, bool use_remote_addr); bool checkUser(const char *username); bool checkPass(const char *password); diff --git a/src/zm_utils.cpp b/src/zm_utils.cpp index d4bb48b73..6ea4e0dae 100644 --- a/src/zm_utils.cpp +++ b/src/zm_utils.cpp @@ -17,299 +17,253 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -//#include "zm_logger.h" -#include "zm.h" #include "zm_utils.h" -#include -#include -#include -#include +#include "zm_config.h" +#include "zm_logger.h" +#include +#include +#include #include /* Definition of AT_* constants */ +#include #include + #if defined(__arm__) #include #endif -#ifdef HAVE_CURL_CURL_H -#include -#endif - -unsigned int sseversion = 0; +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(""); - } - else - return str.substr( startpos, endpos-startpos+1 ); + if ((start_pos == std::string::npos) || (end_pos == std::string::npos)) + return ""; + return str.substr(start_pos, end_pos - start_pos + 1); } -std::string trimSpaces(const std::string &str) { - return trimSet(str, " \t"); -} - -std::string replaceAll(std::string str, std::string from, std::string to) { - if(from.empty()) +std::string ReplaceAll(std::string str, const std::string &old_value, const std::string &new_value) { + if (old_value.empty()) return str; size_t start_pos = 0; - while((start_pos = str.find(from, start_pos)) != std::string::npos) { - str.replace(start_pos, from.length(), to); - start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' + while ((start_pos = str.find(old_value, start_pos)) != std::string::npos) { + str.replace(start_pos, old_value.length(), new_value); + start_pos += new_value.length(); // In case 'new_value' contains 'old_value', like replacing 'x' with 'yx' } return str; } -const std::string stringtf( const char *format, ... ) -{ - va_list ap; - char tempBuffer[8192]; - std::string tempString; +StringVector Split(const std::string &str, char delim) { + std::vector tokens; - va_start(ap, format ); - vsnprintf( tempBuffer, sizeof(tempBuffer), format , ap ); - va_end(ap); + size_t start = 0; + for (size_t end = str.find(delim); end != std::string::npos; end = str.find(delim, start)) { + tokens.push_back(str.substr(start, end - start)); + start = end + 1; + } - tempString = tempBuffer; + tokens.push_back(str.substr(start)); - return( tempString ); + return tokens; } -const std::string stringtf( const std::string format, ... ) -{ - va_list ap; - char tempBuffer[8192]; - std::string tempString; +StringVector Split(const std::string &str, const std::string &delim, size_t limit) { + StringVector tokens; + size_t start = 0; - va_start(ap, format ); - vsnprintf( tempBuffer, sizeof(tempBuffer), format.c_str() , ap ); - va_end(ap); - - tempString = tempBuffer; - - return( tempString ); -} - -bool startsWith(const std::string &haystack, const std::string &needle) { - return( haystack.substr(0, needle.length()) == needle ); -} - -StringVector split(const std::string &string, const std::string &chars, int limit) { - StringVector stringVector; - std::string tempString = string; - std::string::size_type startIndex = 0; - std::string::size_type endIndex = 0; - - //Info( "Looking for '%s' in '%s', limit %d", chars.c_str(), string.c_str(), limit ); do { - // Find delimiters - endIndex = string.find_first_of( chars, startIndex ); - //Info( "Got endIndex at %d", endIndex ); - if ( endIndex > 0 ) { - //Info( "Adding '%s'", string.substr( startIndex, endIndex-startIndex ).c_str() ); - stringVector.push_back( string.substr( startIndex, endIndex-startIndex ) ); + size_t end = str.find_first_of(delim, start); + if (end > 0) { + tokens.push_back(str.substr(start, end - start)); } - if ( endIndex == std::string::npos ) + if (end == std::string::npos) { break; + } // Find non-delimiters - startIndex = tempString.find_first_not_of( chars, endIndex ); - if ( limit && (stringVector.size() == (unsigned int)(limit-1)) ) { - stringVector.push_back( string.substr( startIndex ) ); + start = str.find_first_not_of(delim, end); + if (limit && (tokens.size() == limit - 1)) { + tokens.push_back(str.substr(start)); break; } - //Info( "Got new startIndex at %d", startIndex ); - } while ( startIndex != std::string::npos ); - //Info( "Finished with %d strings", stringVector.size() ); + } while (start != std::string::npos); - return stringVector; + return tokens; } -const std::string join(const StringVector &v, const char * delim=",") { +std::pair PairSplit(const std::string &str, char delim) { + if (str.empty()) + return std::make_pair("", ""); + + size_t pos = str.find(delim); + + if (pos == std::string::npos) + return std::make_pair("", ""); + + return std::make_pair(str.substr(0, pos), str.substr(pos + 1, std::string::npos)); +} + +std::string Join(const StringVector &values, const std::string &delim) { std::stringstream ss; - for (size_t i = 0; i < v.size(); ++i) { - if ( i != 0 ) + for (size_t i = 0; i < values.size(); ++i) { + if (i != 0) ss << delim; - ss << v[i]; + ss << values[i]; } return ss.str(); } -const std::string base64Encode(const std::string &inString) { - static char base64_table[64] = { '\0' }; +std::string stringtf(const char* format, ...) { + va_list args; + va_start(args, format); + va_list args2; + va_copy(args2, args); + int size = vsnprintf(nullptr, 0, format, args); + va_end(args); - if ( !base64_table[0] ) - { + if (size < 0) { + va_end(args2); + throw std::runtime_error("Error during formatting."); + } + size += 1; // Extra space for '\0' + + 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); outString += base64_table[selection]; - } - else - { + } else { outString += base64_table[remainder]; outString += '='; } - } - else - { + } else { outString += base64_table[remainder]; outString += '='; outString += '='; } } - return( outString ); + return outString; } -int split(const char* string, const char delim, std::vector& items) { - if(string == NULL) - 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 == NULL) - 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; - sseversion = 0; + sse_version = 0; #if (defined(__i386__) || defined(__x86_64__)) - /* x86 or x86-64 processor */ - uint32_t r_edx, r_ecx, r_ebx; + __builtin_cpu_init(); -#ifdef __x86_64__ - __asm__ __volatile__( - "push %%rbx\n\t" - "mov $0x0,%%ecx\n\t" - "mov $0x7,%%eax\n\t" - "cpuid\n\t" - "push %%rbx\n\t" - "mov $0x1,%%eax\n\t" - "cpuid\n\t" - "pop %%rax\n\t" - "pop %%rbx\n\t" - : "=d" (r_edx), "=c" (r_ecx), "=a" (r_ebx) - : - : - ); -#else - __asm__ __volatile__( - "push %%ebx\n\t" - "mov $0x0,%%ecx\n\t" - "mov $0x7,%%eax\n\t" - "cpuid\n\t" - "push %%ebx\n\t" - "mov $0x1,%%eax\n\t" - "cpuid\n\t" - "pop %%eax\n\t" - "pop %%ebx\n\t" - : "=d" (r_edx), "=c" (r_ecx), "=a" (r_ebx) - : - : - ); -#endif - - if (r_ebx & 0x00000020) { - sseversion = 52; /* AVX2 */ - Debug(1,"Detected a x86\\x86-64 processor with AVX2"); - } else if (r_ecx & 0x10000000) { - sseversion = 51; /* AVX */ - Debug(1,"Detected a x86\\x86-64 processor with AVX"); - } else if (r_ecx & 0x00100000) { - sseversion = 42; /* SSE4.2 */ - Debug(1,"Detected a x86\\x86-64 processor with SSE4.2"); - } else if (r_ecx & 0x00080000) { - sseversion = 41; /* SSE4.1 */ - Debug(1,"Detected a x86\\x86-64 processor with SSE4.1"); - } else if (r_ecx & 0x00000200) { - sseversion = 35; /* SSSE3 */ - Debug(1,"Detected a x86\\x86-64 processor with SSSE3"); - } else if (r_ecx & 0x00000001) { - sseversion = 30; /* SSE3 */ - Debug(1,"Detected a x86\\x86-64 processor with SSE3"); - } else if (r_edx & 0x04000000) { - sseversion = 20; /* SSE2 */ - Debug(1,"Detected a x86\\x86-64 processor with SSE2"); - } else if (r_edx & 0x02000000) { - sseversion = 10; /* SSE */ - Debug(1,"Detected a x86\\x86-64 processor with SSE"); + 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")) { + sse_version = 51; /* AVX */ + Debug(1, "Detected a x86\\x86-64 processor with AVX"); + } 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")) { + sse_version = 41; /* SSE4.1 */ + Debug(1, "Detected a x86\\x86-64 processor with SSE4.1"); + } 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")) { + sse_version = 30; /* SSE3 */ + Debug(1, "Detected a x86\\x86-64 processor with SSE3"); + } 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")) { + sse_version = 10; /* SSE */ + Debug(1, "Detected a x86\\x86-64 processor with SSE"); } else { - sseversion = 0; - Debug(1,"Detected a x86\\x86-64 processor"); - } + 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 + #ifdef __linux__ unsigned long auxval = getauxval(AT_HWCAP); if (auxval & HWCAP_ARM_NEON) { + #elif defined(__FreeBSD__) + unsigned long auxval = 0; + elf_aux_info(AT_HWCAP, &auxval, sizeof(auxval)); + if (auxval & HWCAP_NEON) { + #else + { + #error Unsupported OS. + #endif Debug(1,"Detected ARM (AArch32) processor with Neon"); neonversion = 1; } else { @@ -329,19 +283,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" @@ -371,7 +325,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 */ @@ -380,63 +334,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[64], buf[64]; - - nowtime = tv.tv_sec; - nowtm = localtime(&nowtime); - strftime(tmbuf, sizeof tmbuf, "%Y-%m-%d %H:%M:%S", nowtm); - snprintf(buf, sizeof buf, "%s.%06ld", tmbuf, tv.tv_usec); - return buf; -} - -std::string UriDecode( const std::string &encoded ) { -#ifdef HAVE_LIBCURL - CURL *curl = curl_easy_init(); - int outlength; - char *cres = curl_easy_unescape(curl, encoded.c_str(), encoded.length(), &outlength); - std::string res(cres, cres + outlength); - curl_free(cres); - curl_easy_cleanup(curl); - return res; -#else -Warning("ZM Compiled without LIBCURL. UriDecoding not implemented."); - return encoded; -#endif -} - -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 9a8dd1948..d5ee88202 100644 --- a/src/zm_utils.h +++ b/src/zm_utils.h @@ -20,49 +20,149 @@ #ifndef ZM_UTILS_H #define ZM_UTILS_H -#include -#include +#include "zm_define.h" +#include +#include +#include +#include +#include +#include +#include "span.hpp" +#include #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); - -const std::string stringtf( const char *format, ... ); -const std::string stringtf( const std::string &format, ... ); - -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 * ); - -const std::string base64Encode( const std::string &inString ); -void string_toupper(std::string& str); - -int split(const char* string, const char delim, std::vector& items); -int pairsplit(const char* string, const char delim, std::string& name, std::string& value); - -inline int max( int a, int b ) -{ - return( a>=b?a:b ); +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; } -inline int min( int a, int b ) -{ - return( a<=b?a:b ); +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); + +std::string Join(const StringVector &values, const std::string &delim = ","); + +inline bool StartsWith(const std::string &haystack, const std::string &needle) { + return (haystack.substr(0, needle.length()) == needle); } -void* sse2_aligned_memcpy(void* dest, const void* src, size_t bytes); -void timespec_diff(struct timespec *start, struct timespec *end, struct timespec *diff); +__attribute__((format(printf, 1, 2))) +std::string stringtf(const char* format, ...); -void hwcaps_detect(); -extern unsigned int sseversion; +std::string ByteArrayToHexString(nonstd::span bytes); + +std::string Base64Encode(const std::string &str); + +std::string TimevalToString(timeval tv); + +extern unsigned int sse_version; extern unsigned int neonversion; +void HwCapsDetect(); +void *sse2_aligned_memcpy(void *dest, const void *src, size_t bytes); + +void touch(const char *pathname); + +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{}); +} + +// C++17 std::data (TODO: remove this once C++17 is supported) +template +constexpr auto data(C &c) -> decltype(c.data()) { return c.data(); } + +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_; +}; -char *timeval_to_string( struct timeval tv ); -std::string UriDecode( const std::string &encoded ); -void touch( const char *pathname ); #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 309c507c0..000000000 --- a/src/zm_video.cpp +++ /dev/null @@ -1,590 +0,0 @@ -// Copyright (C) 2001-2017 ZoneMinder LLC -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// -#include "zm.h" -#include "zm_video.h" -#include "zm_image.h" -#include "zm_utils.h" -#include "zm_rgb.h" -#include -#include -#include - -VideoWriter::VideoWriter( - const char* p_container, - const char* p_codec, - const char* p_path, - const unsigned int p_width, - const unsigned int p_height, - const unsigned int p_colours, - const unsigned int p_subpixelorder) : - container(p_container), - codec(p_codec), - path(p_path), - width(p_width), - height(p_height), - colours(p_colours), - subpixelorder(p_subpixelorder), - frame_count(0) { - Debug(7, "Video object created"); - - /* Parameter checking */ - if ( path.empty() ) { - Error("Invalid file path"); - } - if ( !width || !height ) { - Error("Invalid width or height"); - } -} - -VideoWriter::~VideoWriter() { - Debug(7, "Video object destroyed"); -} - -int VideoWriter::Reset(const char* new_path) { - /* Common variables reset */ - - /* If there is a new path, use it */ - if ( new_path != NULL ) { - 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 != NULL ) { - 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 == NULL ) { - 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 == NULL ) { - 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, NULL, &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 == NULL ) { - Error("NULL Encoder parameters vector pointer"); - return -1; - } - - if ( str == NULL ) { - Error("NULL Encoder parameters string"); - return -2; - } - - vec->clear(); - - if ( str[0] == 0 ) { - /* Empty */ - return 0; - } - - std::string line; - std::stringstream ss(str); - size_t valueoffset; - size_t valuelen; - unsigned int lineno = 0; - EncoderParameter_t param; - - while ( std::getline(ss, line) ) { - lineno++; - - /* Remove CR if exists */ - if ( line.length() >= 1 && line[line.length()-1] == '\r' ) { - line.erase(line.length() - 1); - } - - /* Skip comments and empty lines */ - if ( line.empty() || line[0] == '#' ) { - continue; - } - - valueoffset = line.find('='); - if ( valueoffset == std::string::npos || valueoffset+1 >= line.length() || valueoffset == 0 ) { - Warning("Failed parsing encoder parameters line %d: Invalid pair", lineno); - continue; - } - - if ( valueoffset > (sizeof(param.pname) - 1 ) ) { - Warning("Failed parsing encoder parameters line %d: Name too long", lineno); - continue; - } - - valuelen = line.length() - (valueoffset+1); - - if ( valuelen > (sizeof(param.pvalue) - 1 ) ) { - Warning("Failed parsing encoder parameters line %d: Value too long", lineno); - continue; - } - - /* Copy and NULL terminate */ - line.copy(param.pname, valueoffset, 0); - line.copy(param.pvalue, valuelen, valueoffset+1); - param.pname[valueoffset] = 0; - param.pvalue[valuelen] = 0; - - /* Push to the vector */ - vec->push_back(param); - - Debug(7, "Parsed encoder parameter: %s = %s", param.pname, param.pvalue); - } - - Debug(7, "Parsed %d lines", lineno); - - return 0; -} - - diff --git a/src/zm_video.h b/src/zm_video.h deleted file mode 100644 index 46d7c3635..000000000 --- a/src/zm_video.h +++ /dev/null @@ -1,174 +0,0 @@ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#ifndef ZM_VIDEO_H -#define ZM_VIDEO_H - -#include "zm.h" -#include "zm_rgb.h" -#include "zm_utils.h" -#include "zm_ffmpeg.h" -#include "zm_buffer.h" -#include "zm_swscale.h" - -/* -#define HAVE_LIBX264 1 -#define HAVE_LIBMP4V2 1 -#define HAVE_X264_H 1 -#define HAVE_MP4_H 1 -*/ - -#if HAVE_MP4V2_MP4V2_H -#include -#endif -#if HAVE_MP4V2_H -#include -#endif -#if HAVE_MP4_H -#include -#endif - -#if HAVE_X264_H -#ifdef __cplusplus -extern "C" { -#endif -#include -#ifdef __cplusplus -} -#endif -#endif - -/* Structure for user parameters to the encoder */ -struct EncoderParameter_t { - char pname[48]; - char pvalue[48]; - -}; -int ParseEncoderParameters(const char* str, std::vector* vec); - -/* VideoWriter is a generic interface that ZM uses to save events as videos */ -/* It is relatively simple and the functions are pure virtual, so they must be implemented by the deriving class */ - -class VideoWriter { - -protected: - std::string container; - std::string codec; - std::string path; - unsigned int width; - unsigned int height; - unsigned int colours; - unsigned int subpixelorder; - - unsigned int frame_count; - -public: - VideoWriter(const char* p_container, const char* p_codec, const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder); - virtual ~VideoWriter(); - virtual int Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time) = 0; - virtual int Encode(const Image* img, const unsigned int frame_time) = 0; - virtual int Open() = 0; - virtual int Close() = 0; - virtual int Reset(const char* new_path = NULL); - - 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 = NULL); - ~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 = NULL); - -}; -#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 ad04124fa..249ff222a 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -18,353 +18,445 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#define __STDC_FORMAT_MACROS 1 - - -#include "zm.h" #include "zm_videostore.h" -#include -#include -#include +#include "zm_logger.h" +#include "zm_monitor.h" +#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[] = { +#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( const char *filename_in, const char *format_in, AVStream *p_video_in_stream, + AVCodecContext *p_video_in_ctx, AVStream *p_audio_in_stream, - Monitor *monitor - ) { + 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_ctx(p_video_in_ctx), + video_out_ctx(nullptr), + video_in_stream(p_video_in_stream), + audio_in_stream(p_audio_in_stream), + audio_in_codec(nullptr), + audio_in_ctx(p_audio_in_ctx), + audio_out_codec(nullptr), + audio_out_ctx(nullptr), + video_in_frame(nullptr), + in_frame(nullptr), + out_frame(nullptr), + hw_frame(nullptr), + packets_written(0), + frame_count(0), + hw_device_ctx(nullptr), + resample_ctx(nullptr), + fifo(nullptr), + converted_in_samples(nullptr), + filename(filename_in), + format(format_in), + video_first_pts(0), + video_first_dts(0), + audio_first_pts(0), + audio_first_dts(0), + video_last_pts(AV_NOPTS_VALUE), + audio_last_pts(AV_NOPTS_VALUE), + next_dts(nullptr), + audio_next_pts(0), + max_stream_index(-1) +{ + FFMPEGInit(); + swscale.init(); +} // VideoStore::VideoStore - video_in_stream = p_video_in_stream; - audio_in_stream = p_audio_in_stream; +bool VideoStore::open() { + Debug(1, "Opening video storage stream %s format: %s", filename, format); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - //video_in_ctx = avcodec_alloc_context3(NULL); - //avcodec_parameters_to_context(video_in_ctx, - //video_in_stream->codecpar); - //video_in_ctx->time_base = video_in_stream->time_base; -// zm_dump_codecpar( video_in_stream->codecpar ); -#else -#endif - - // In future, we should just pass in the codec context instead of the stream. Don't really need the stream. - video_in_ctx = video_in_stream->codec; - - // store ins in variables local to class - filename = filename_in; - format = format_in; - - Info("Opening video storage stream %s format: %s", filename, format); - - int ret = avformat_alloc_output_context2(&oc, NULL, NULL, filename); - if ( ret < 0 ) { + int ret = avformat_alloc_output_context2(&oc, nullptr, nullptr, filename); + if (ret < 0) { Warning( "Could not create video storage stream %s as no out ctx" " could be assigned based on filename: %s", filename, av_make_error_string(ret).c_str()); - } else { - Debug(4, "Success allocating out format ctx"); } // Couldn't deduce format from filename, trying from format name - if ( !oc ) { - avformat_alloc_output_context2(&oc, NULL, format, filename); - if ( !oc ) { + if (!oc) { + avformat_alloc_output_context2(&oc, nullptr, format, filename); + if (!oc) { Error( "Could not create video storage stream %s as no out ctx" " could not be assigned based on filename or format %s", filename, format); - return; - } else { - Debug(4, "Success allocating out ctx"); + return false; } - } // end if ! oc + } // end if ! oc - AVDictionary *pmetadata = NULL; - int dsr = - av_dict_set(&pmetadata, "title", "Zoneminder Security Recording", 0); - if ( dsr < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); + AVDictionary *pmetadata = nullptr; + ret = av_dict_set(&pmetadata, "title", "Zoneminder Security Recording", 0); + if (ret < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__); oc->metadata = pmetadata; out_format = oc->oformat; out_format->flags |= AVFMT_TS_NONSTRICT; // allow non increasing dts + AVCodec *video_out_codec = nullptr; - video_out_codec = avcodec_find_encoder(video_in_ctx->codec_id); - if ( !video_out_codec ) { -#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) - Fatal("Could not find encoder for '%s'", avcodec_get_name(video_out_ctx->codec_id)); -#else - Fatal("Could not find encoder for '%d'", video_out_ctx->codec_id); -#endif - } - - video_out_stream = avformat_new_stream(oc, NULL); - if ( !video_out_stream ) { - Error("Unable to create video out stream"); - return; + 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 { - Debug(2, "Success creating video out stream"); + 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); + + 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; + } + 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) { + // default to h264 + //Debug(2, "Defaulting to H264"); + //wanted_codec = AV_CODEC_ID_H264; + // FIXME what is the optimal codec? Probably low latency h264 which is effectively mjpeg + } else { + if (AV_CODEC_ID_H264 != 27 and wanted_codec > 3) { + // Older ffmpeg had AV_CODEC_ID_MPEG2VIDEO_XVMC at position 3 has been deprecated + wanted_codec += 1; + } + Debug(2, "Codec wanted %d %s", wanted_codec, avcodec_get_name((AVCodecID)wanted_codec)); + } + std::string wanted_encoder = monitor->Encoder(); + + for (unsigned int i = 0; i < sizeof(codec_data) / sizeof(*codec_data); i++) { + chosen_codec_data = &codec_data[i]; + if (wanted_encoder != "" and wanted_encoder != "auto") { + if (wanted_encoder != codec_data[i].codec_name) { + Debug(1, "Not the right codec name %s != %s", codec_data[i].codec_name, wanted_encoder.c_str()); + continue; + } + } + if (wanted_codec and (codec_data[i].codec_id != wanted_codec)) { + Debug(1, "Not the right codec %d %s != %d %s", + codec_data[i].codec_id, + avcodec_get_name(codec_data[i].codec_id), + wanted_codec, + avcodec_get_name((AVCodecID)wanted_codec) + ); + continue; + } + + video_out_codec = avcodec_find_encoder_by_name(codec_data[i].codec_name); + if (!video_out_codec) { + Debug(1, "Didn't find encoder for %s", codec_data[i].codec_name); + continue; + } + Debug(1, "Found video codec for %s", codec_data[i].codec_name); + video_out_ctx = avcodec_alloc_context3(video_out_codec); + if (oc->oformat->flags & AVFMT_GLOBALHEADER) { + video_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } + + // When encoding, we are going to use the timestamp values instead of packet pts/dts + video_out_ctx->time_base = AV_TIME_BASE_Q; + video_out_ctx->codec_id = codec_data[i].codec_id; + video_out_ctx->pix_fmt = codec_data[i].hw_pix_fmt; + Debug(1, "Setting pix fmt to %d %s", codec_data[i].hw_pix_fmt, av_get_pix_fmt_name(codec_data[i].hw_pix_fmt)); + video_out_ctx->level = 32; + + // Don't have an input stream, so need to tell it what we are sending it, or are transcoding + video_out_ctx->width = monitor->Width(); + video_out_ctx->height = monitor->Height(); + video_out_ctx->codec_type = AVMEDIA_TYPE_VIDEO; + + if (video_out_ctx->codec_id == AV_CODEC_ID_H264) { + video_out_ctx->bit_rate = 2000000; + video_out_ctx->gop_size = 12; + video_out_ctx->max_b_frames = 1; + } else if (video_out_ctx->codec_id == AV_CODEC_ID_MPEG2VIDEO) { + /* just for testing, we also add B frames */ + video_out_ctx->max_b_frames = 2; + } else if (video_out_ctx->codec_id == AV_CODEC_ID_MPEG1VIDEO) { + /* Needed to avoid using macroblocks in which some coeffs overflow. + * This does not happen with normal video, it just happens here as + * the motion of the chroma plane does not match the luma plane. */ + video_out_ctx->mb_decision = 2; + } +#if HAVE_LIBAVUTIL_HWCONTEXT_H && 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; + } + + 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() + ); + } else { + Debug(1, "Can't open video codec (%s) %s", + video_out_codec->name, + av_make_error_string(ret).c_str() + ); + } + video_out_codec = nullptr; + } + + AVDictionaryEntry *e = nullptr; + while ((e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr) { + Warning("Encoder Option %s not recognized by ffmpeg codec", e->key); + } + 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); + 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 + Debug(2, "Success opening codec"); + + 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 + max_stream_index = video_out_stream->index; + video_out_stream->time_base = video_in_stream ? video_in_stream->time_base : AV_TIME_BASE_Q; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - // by allocating our own copy, we don't run into the problems when we free the streams - video_out_ctx = avcodec_alloc_context3(video_out_codec); - // Since we are not re-encoding, all we have to do is copy the parameters - // Copy params from instream to ctx - ret = avcodec_parameters_to_context(video_out_ctx, video_in_stream->codecpar); - if ( ret < 0 ) { - Error("Could not initialize video_out_ctx parameters"); - return; - } -#else - video_out_ctx = video_out_stream->codec; - // This will wipe out the codec defaults - ret = avcodec_copy_context(video_out_ctx, video_in_ctx); - if ( ret < 0 ) { - Fatal("Unable to copy in video ctx to out video ctx %s", - av_make_error_string(ret).c_str()); - } else { - Debug(3, "Success copying ctx"); - } -#endif - - // Just copy them from the in, no reason to choose different - video_out_ctx->time_base = video_in_ctx->time_base; - if ( ! (video_out_ctx->time_base.num && video_out_ctx->time_base.den) ) { - Debug(2,"No timebase found in video in context, defaulting to Q"); - video_out_ctx->time_base = AV_TIME_BASE_Q; - } - - zm_dump_codec(video_out_ctx); - -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - //// Fix deprecated formats - switch ( video_out_ctx->pix_fmt ) { - case AV_PIX_FMT_YUVJ422P : - video_out_ctx->pix_fmt = AV_PIX_FMT_YUV422P; - break; - case AV_PIX_FMT_YUVJ444P : - video_out_ctx->pix_fmt = AV_PIX_FMT_YUV444P; - break; - case AV_PIX_FMT_YUVJ440P : - video_out_ctx->pix_fmt = AV_PIX_FMT_YUV440P; - break; - case AV_PIX_FMT_NONE : - case AV_PIX_FMT_YUVJ420P : - default: - video_out_ctx->pix_fmt = AV_PIX_FMT_YUV420P; - break; - } - - if ( !video_out_ctx->codec_tag ) { - Debug(2, "No codec_tag"); - if ( - !oc->oformat->codec_tag - || - av_codec_get_id(oc->oformat->codec_tag, video_in_ctx->codec_tag) == video_out_ctx->codec_id - || - av_codec_get_tag(oc->oformat->codec_tag, video_in_ctx->codec_id) <= 0 - ) { - Warning("Setting codec tag"); - video_out_ctx->codec_tag = video_in_ctx->codec_tag; - } - } -#endif - - video_out_stream->time_base = video_in_stream->time_base; - if ( video_in_stream->avg_frame_rate.num ) { - Debug(3,"Copying avg_frame_rate (%d/%d)", - video_in_stream->avg_frame_rate.num, - video_in_stream->avg_frame_rate.den - ); - video_out_stream->avg_frame_rate = video_in_stream->avg_frame_rate; - } -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - if ( video_in_stream->r_frame_rate.num ) { - Debug(3,"Copying r_frame_rate (%d/%d) to out (%d/%d)", - video_in_stream->r_frame_rate.num, - video_in_stream->r_frame_rate.den , - video_out_stream->r_frame_rate.num, - video_out_stream->r_frame_rate.den - ); - video_out_stream->r_frame_rate = video_in_stream->r_frame_rate; - } -#endif - Debug(3, - "Time bases: VIDEO in stream (%d/%d) in codec: (%d/%d) out " - "stream: (%d/%d) out codec (%d/%d)", - video_in_stream->time_base.num, video_in_stream->time_base.den, - video_in_ctx->time_base.num, video_in_ctx->time_base.den, - video_out_stream->time_base.num, video_out_stream->time_base.den, - video_out_ctx->time_base.num, video_out_ctx->time_base.den); - - if ( oc->oformat->flags & AVFMT_GLOBALHEADER ) { -#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) - video_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; -#else - video_out_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; -#endif - } - -#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) - /* I'm not entirely sure that this is a good idea. We may have to do it someday but really only when transcoding - * * think what I was trying to achieve here was to have zm_dump_codecpar output nice info - * */ -#if 0 - AVDictionary *opts = 0; - 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 = NULL; - } - - AVDictionaryEntry *e = NULL; - while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL ) { - Warning("Encoder Option %s not recognized by ffmpeg codec", e->key); - } - ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_out_ctx); - if ( ret < 0 ) { - Error("Could not initialize video_out_ctx parameters"); - return; - } else { - zm_dump_codec(video_out_ctx); - } -#else - ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_in_ctx); - if ( ret < 0 ) { - Error("Could not initialize video_out_ctx parameters"); - return; - } else { - zm_dump_codec(video_out_ctx); - } -#endif - zm_dump_codecpar(video_in_stream->codecpar); - zm_dump_codecpar(video_out_stream->codecpar); -#endif - - Monitor::Orientation orientation = monitor->getOrientation(); - if ( orientation ) { - if ( orientation == Monitor::ROTATE_0 ) { - } else if ( orientation == Monitor::ROTATE_90 ) { - dsr = av_dict_set(&video_out_stream->metadata, "rotate", "90", 0); - if ( dsr < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); - } else if ( orientation == Monitor::ROTATE_180 ) { - dsr = av_dict_set(&video_out_stream->metadata, "rotate", "180", 0); - if ( dsr < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); - } else if ( orientation == Monitor::ROTATE_270 ) { - dsr = av_dict_set(&video_out_stream->metadata, "rotate", "270", 0); - if ( dsr < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); - } else { - Warning("Unsupported Orientation(%d)", orientation); - } - } - - converted_in_samples = NULL; - audio_out_codec = NULL; - audio_in_codec = NULL; - audio_in_ctx = NULL; - audio_out_stream = NULL; - in_frame = NULL; - out_frame = NULL; -#if defined(HAVE_LIBSWRESAMPLE) || defined(HAVE_LIBAVRESAMPLE) - resample_ctx = NULL; - fifo = NULL; -#endif - video_first_pts = 0; - video_first_dts = 0; - - audio_first_pts = 0; - audio_first_dts = 0; - - if ( audio_in_stream ) { - Debug(3, "Have audio stream"); - - if ( -#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 (audio_in_stream and audio_in_ctx) { + Debug(2, "Have audio_in_stream %p", audio_in_stream); + 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"); - return; - } + } else { + audio_in_ctx = avcodec_alloc_context3(audio_out_codec); + ret = avcodec_parameters_to_context(audio_in_ctx, audio_in_stream->codecpar); + if (ret < 0) + Error("Failure from avcodec_parameters_to_context %s", + av_make_error_string(ret).c_str()); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - audio_out_stream = avformat_new_stream(oc, NULL); - audio_out_ctx = avcodec_alloc_context3(audio_out_codec); - if ( !audio_out_ctx ) { - Error("could not allocate codec ctx for AAC"); - audio_out_stream = NULL; - return; - } -#else - audio_out_stream = avformat_new_stream(oc, audio_out_codec); - audio_out_ctx = audio_out_stream->codec; -#endif - audio_out_stream->time_base = audio_in_stream->time_base; + audio_in_ctx->time_base = audio_in_stream->time_base; - if ( !setup_resampler() ) { - return; - } + audio_out_ctx = avcodec_alloc_context3(audio_out_codec); + if (!audio_out_ctx) { + Error("could not allocate codec ctx for AAC"); + return false; + } + + audio_out_stream = avformat_new_stream(oc, audio_out_codec); + audio_out_stream->time_base = audio_in_stream->time_base; + + if (!setup_resampler()) { + return false; + } + } // end if found AAC codec } else { Debug(2, "Got AAC"); - audio_out_stream = avformat_new_stream(oc, NULL); - if ( !audio_out_stream ) { + // normally we want to pass params from codec in here + // but since we are doing audio passthrough we don't care + audio_out_stream = avformat_new_stream(oc, audio_out_codec); + if (!audio_out_stream) { Error("Could not allocate new stream"); - return; + return false; } audio_out_stream->time_base = audio_in_stream->time_base; -#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; + return false; } // We don't actually care what the time_base is.. - audio_out_ctx->time_base = audio_in_stream->time_base; + audio_out_ctx->time_base = audio_in_ctx->time_base; // Copy params from instream to ctx ret = avcodec_parameters_to_context( 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 = NULL; - return; - } // 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 { @@ -372,12 +464,8 @@ VideoStore::VideoStore( } } // 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 @@ -386,19 +474,16 @@ VideoStore::VideoStore( //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; } -} // VideoStore::VideoStore -bool VideoStore::open() { - int ret; /* open the out file, if needed */ - if ( !(out_format->flags & AVFMT_NOFILE) ) { - ret = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, NULL, NULL); - if ( ret < 0 ) { + if (!(out_format->flags & AVFMT_NOFILE)) { + ret = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, nullptr, nullptr); + if (ret < 0) { Error("Could not open out file '%s': %s", filename, - av_make_error_string(ret).c_str()); + av_make_error_string(ret).c_str()); return false; } } @@ -406,265 +491,237 @@ 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 = NULL; - // av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); - // Shiboleth reports that this may break seeking in mp4 before it downloads - //av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov", 0); - // av_dict_set(&opts, "movflags", - // "frag_keyframe+empty_moov+default_base_moof", 0); - if ( (ret = avformat_write_header(oc, &opts)) < 0 ) { - // if ((ret = avformat_write_header(oc, &opts)) < 0) { - Warning("Unable to set movflags to frag_custom+dash+delay_moov"); - /* Write the stream header, if any. */ - ret = avformat_write_header(oc, NULL); - } else if (av_dict_count(opts) != 0) { - Warning("some options not set"); + 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) { + 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 ( opts ) av_dict_free(&opts); - if ( ret < 0 ) { + if ((ret = avformat_write_header(oc, &opts)) < 0) { + // we crash if we try again + if (ENOSPC != ret) { + Warning("Unable to set movflags trying with defaults.%d %s", + ret, av_make_error_string(ret).c_str()); + + ret = avformat_write_header(oc, nullptr); + Debug(1, "Done %d", ret); + } else { + Error("ENOSPC. fail"); + } + } 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) { + Debug(1, "Encoder Option %s=>%s", e->key, e->value); + if (!e->value) { + av_dict_set(&opts, e->key, nullptr, 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()); - /* free the stream */ + filename, av_make_error_string(ret).c_str()); avio_closep(&oc->pb); - //avformat_free_context(oc); return false; } + + zm_dump_stream_format(oc, 0, 0, 1); + if (audio_out_stream) zm_dump_stream_format(oc, 1, 0, 1); return true; -} // end VideoStore::open() +} // end bool VideoStore::open() -VideoStore::~VideoStore() { +void VideoStore::flush_codecs() { + // The codec queues data. We need to send a flush command and out + // whatever we get. Failures are not fatal. + AVPacket pkt; + // Without these we seg fault becuse av_init_packet doesn't init them + pkt.data = nullptr; + pkt.size = 0; + av_init_packet(&pkt); - if ( oc->pb ) { + // I got crashes if the codec didn't do DELAY, so let's test for it. + if (video_out_ctx && video_out_ctx->codec && (video_out_ctx->codec->capabilities & AV_CODEC_CAP_DELAY)) { + // Put encoder into flushing mode + 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); + write_packet(&pkt, video_out_stream); + zm_av_packet_unref(&pkt); + } // while have buffered frames + Debug(1, "Done writing buffered video."); + } // end if have delay capability - if ( audio_out_codec ) { + if (audio_out_codec) { + // The codec queues data. We need to send a flush command and out + // whatever we get. Failures are not fatal. - // The codec queues data. We need to send a flush command and out - // whatever we get. Failures are not fatal. - AVPacket pkt; - // Without these we seg fault I don't know why. - pkt.data = NULL; - pkt.size = 0; - av_init_packet(&pkt); - - int frame_size = audio_out_ctx->frame_size; - /* - * At the end of the file, we pass the remaining samples to - * the encoder. */ - while ( zm_resample_get_delay(resample_ctx, audio_out_ctx->sample_rate) ) { - zm_resample_audio(resample_ctx, NULL, out_frame); - - if ( zm_add_samples_to_fifo(fifo, out_frame) ) { - // Should probably set the frame size to what is reported FIXME - if ( zm_get_samples_from_fifo(fifo, out_frame) ) { - if ( zm_send_frame_receive_packet(audio_out_ctx, out_frame, pkt) ) { - pkt.stream_index = audio_out_stream->index; - - av_packet_rescale_ts(&pkt, - audio_out_ctx->time_base, - audio_out_stream->time_base); - write_packet(&pkt, audio_out_stream); - } - } // end if data returned from fifo - } - - } // end if have buffered samples in the resampler - - Debug(2, "av_audio_fifo_size = %d", av_audio_fifo_size(fifo)); - while ( av_audio_fifo_size(fifo) > 0 ) { - /* Take one frame worth of audio samples from the FIFO buffer, - * encode it and write it to the output file. */ - - Debug(1, "Remaining samples in fifo for AAC codec frame_size %d > fifo size %d", - frame_size, av_audio_fifo_size(fifo)); - - // SHould probably set the frame size to what is reported FIXME - if ( av_audio_fifo_read(fifo, (void **)out_frame->data, frame_size) ) { - if ( zm_send_frame_receive_packet(audio_out_ctx, out_frame, pkt) ) { - pkt.stream_index = audio_out_stream->index; + int frame_size = audio_out_ctx->frame_size; + /* + * At the end of the file, we pass the remaining samples to + * the encoder. */ + while (zm_resample_get_delay(resample_ctx, audio_out_ctx->sample_rate)) { + zm_resample_audio(resample_ctx, nullptr, out_frame); + if (zm_add_samples_to_fifo(fifo, out_frame)) { + // Should probably set the frame size to what is reported FIXME + if (zm_get_samples_from_fifo(fifo, out_frame)) { + if (zm_send_frame_receive_packet(audio_out_ctx, out_frame, pkt) > 0) { av_packet_rescale_ts(&pkt, audio_out_ctx->time_base, audio_out_stream->time_base); write_packet(&pkt, audio_out_stream); + zm_av_packet_unref(&pkt); } } // end if data returned from fifo - } // end while still data in the fifo + } + } // end while have buffered samples in the resampler -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - // Put encoder into flushing mode - avcodec_send_frame(audio_out_ctx, NULL); -#endif + Debug(2, "av_audio_fifo_size = %d", av_audio_fifo_size(fifo)); + while (av_audio_fifo_size(fifo) > 0) { + /* Take one frame worth of audio samples from the FIFO buffer, + * encode it and write it to the output file. */ - while (1) { - if ( ! zm_receive_packet(audio_out_ctx, pkt) ) { - Debug(1, "No more packets"); - break; + Debug(1, "Remaining samples in fifo for AAC codec frame_size %d > fifo size %d", + frame_size, av_audio_fifo_size(fifo)); + + // SHould probably set the frame size to what is reported FIXME + if (av_audio_fifo_read(fifo, (void **)out_frame->data, frame_size)) { + if (zm_send_frame_receive_packet(audio_out_ctx, out_frame, pkt)) { + pkt.stream_index = audio_out_stream->index; + + av_packet_rescale_ts(&pkt, + audio_out_ctx->time_base, + audio_out_stream->time_base); + write_packet(&pkt, audio_out_stream); + zm_av_packet_unref(&pkt); } + } // end if data returned from fifo + } // end while still data in the fifo - dumpPacket(&pkt, "raw from encoder"); - av_packet_rescale_ts(&pkt, audio_out_ctx->time_base, audio_out_stream->time_base); - dumpPacket(audio_out_stream, &pkt, "writing flushed packet"); - write_packet(&pkt, audio_out_stream); - zm_av_packet_unref(&pkt); - } // while have buffered frames - } // end if audio_out_codec + // Put encoder into flushing mode + avcodec_send_frame(audio_out_ctx, nullptr); + + while (true) { + if (0 >= zm_receive_packet(audio_out_ctx, pkt)) { + Debug(1, "No more packets"); + break; + } + + ZM_DUMP_PACKET(pkt, "raw from encoder"); + av_packet_rescale_ts(&pkt, audio_out_ctx->time_base, audio_out_stream->time_base); + ZM_DUMP_STREAM_PACKET(audio_out_stream, pkt, "writing flushed packet"); + write_packet(&pkt, audio_out_stream); + zm_av_packet_unref(&pkt); + } // while have buffered frames + } // end if audio_out_codec +} // end flush_codecs + +VideoStore::~VideoStore() { + if (oc->pb) { + flush_codecs(); // Flush Queues - Debug(1, "Flushing interleaved queues"); - av_interleaved_write_frame(oc, NULL); + 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) ) { - oc->pb = NULL; + 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."); } - } // end if ( oc->pb ) + oc->pb = nullptr; + } // end if oc->pb // I wonder if we should be closing the file first. // I also wonder if we really need to be doing all the ctx // allocation/de-allocation constantly, or whether we can just re-use it. // Just do a file open/close/writeheader/etc. // What if we were only doing audio recording? - if ( video_out_stream ) { -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - // We allocate and copy in newer ffmpeg, so need to free it - //avcodec_free_context(&video_in_ctx); -#endif - video_in_ctx = NULL; - if ( video_out_codec ) { - avcodec_close(video_out_ctx); - Debug(4, "Success closing video_out_ctx"); - video_out_codec = NULL; - } // end if video_out_codec -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + video_in_ctx = nullptr; + + if (video_out_ctx) { + avcodec_close(video_out_ctx); + Debug(3, "Freeing video_out_ctx"); avcodec_free_context(&video_out_ctx); -#endif - video_out_ctx = NULL; - } // 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 ( audio_in_codec ) { - avcodec_close(audio_in_ctx); - Debug(4, "Success closing audio_in_ctx"); - audio_in_codec = NULL; - } // end if audio_in_codec + if (audio_out_stream) { + audio_in_codec = nullptr; -#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"); - audio_in_ctx = NULL; - - if ( audio_out_ctx ) { - avcodec_close(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 } - audio_out_ctx = NULL; -#if defined(HAVE_LIBAVRESAMPLE) || defined(HAVE_LIBSWRESAMPLE) - if ( resample_ctx ) { - if ( fifo ) { + if (resample_ctx) { + if (fifo) { av_audio_fifo_free(fifo); - fifo = NULL; + 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 = NULL; + in_frame = nullptr; } - if ( out_frame ) { + if (out_frame) { av_frame_free(&out_frame); - out_frame = NULL; + out_frame = nullptr; } - if ( converted_in_samples ) { + if (converted_in_samples) { av_free(converted_in_samples); - converted_in_samples = NULL; + converted_in_samples = nullptr; } -#endif } // end if audio_out_stream + Debug(4, "free context"); /* free the streams */ avformat_free_context(oc); delete[] next_dts; + next_dts = nullptr; } // VideoStore::~VideoStore() bool VideoStore::setup_resampler() { -#if !defined(HAVE_LIBSWRESAMPLE) && !defined(HAVE_LIBAVRESAMPLE) - Error( - "Not built with resample library. " - "Cannot do audio conversion to AAC"); - 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 LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) -#else -#if 0 - ret = avcodec_copy_context(audio_in_ctx, audio_in_stream->codec); - if ( ret < 0 ) { - Fatal("Unable to copy in video ctx to out video ctx %s", - av_make_error_string(ret).c_str()); - } else { - Debug(3, "Success copying ctx"); - } -#endif -#endif - // if the codec is already open, nothing is done. - if ( (ret = avcodec_open2(audio_in_ctx, audio_in_codec, NULL)) < 0 ) { + if ((ret = avcodec_open2(audio_in_ctx, audio_in_codec, nullptr)) < 0) { Error("Can't open audio in codec!"); return false; } @@ -672,7 +729,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"); @@ -681,71 +738,68 @@ bool VideoStore::setup_resampler() { /* put sample parameters */ audio_out_ctx->bit_rate = audio_in_ctx->bit_rate <= 32768 ? audio_in_ctx->bit_rate : 32768; audio_out_ctx->sample_rate = audio_in_ctx->sample_rate; + audio_out_ctx->sample_fmt = audio_in_ctx->sample_fmt; audio_out_ctx->channels = audio_in_ctx->channels; audio_out_ctx->channel_layout = audio_in_ctx->channel_layout; - audio_out_ctx->sample_fmt = audio_in_ctx->sample_fmt; -#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) - if ( !audio_out_ctx->channel_layout ) { - Debug(3, "Correcting channel layout from (%d) to (%d)", + 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) ) { - Debug(2, "Encoder does not support sample format %s, setting to FLTP", - av_get_sample_fmt_name(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; } - audio_out_ctx->time_base = (AVRational){1, audio_out_ctx->sample_rate}; + // Example code doesn't set the codec tb. I think it just uses whatever defaults + //audio_out_ctx->time_base = (AVRational){1, audio_out_ctx->sample_rate}; - AVDictionary *opts = NULL; - if ( (ret = av_dict_set(&opts, "strict", "experimental", 0)) < 0 ) { + AVDictionary *opts = nullptr; + // Needed to allow AAC + 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 = NULL; - audio_out_ctx = NULL; - audio_out_stream = NULL; + audio_out_codec = nullptr; + audio_out_ctx = nullptr; + audio_out_stream = nullptr; return false; } zm_dump_codec(audio_out_ctx); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - ret = avcodec_parameters_from_context( - audio_out_stream->codecpar, audio_out_ctx); - if ( ret < 0 ) { + audio_out_stream->time_base = (AVRational){1, audio_out_ctx->sample_rate}; + 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 " @@ -756,115 +810,82 @@ bool VideoStore::setup_resampler() { audio_out_ctx->time_base.num, audio_out_ctx->time_base.den); Debug(1, - "Audio in bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) " - "layout(%d) frame_size(%d)", + "Audio in bit_rate (%" AV_PACKET_DURATION_FMT ") sample_rate(%d) channels(%d) fmt(%d) layout(%" PRIi64 ") frame_size(%d)", audio_in_ctx->bit_rate, audio_in_ctx->sample_rate, audio_in_ctx->channels, audio_in_ctx->sample_fmt, audio_in_ctx->channel_layout, audio_in_ctx->frame_size); Debug(1, - "Audio out bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) " - "layout(%d) frame_size(%d)", + "Audio out context bit_rate (%" AV_PACKET_DURATION_FMT ") sample_rate(%d) channels(%d) fmt(%d) layout(% " PRIi64 ") frame_size(%d)", audio_out_ctx->bit_rate, audio_out_ctx->sample_rate, audio_out_ctx->channels, audio_out_ctx->sample_fmt, audio_out_ctx->channel_layout, audio_out_ctx->frame_size); + Debug(1, + "Audio out stream bit_rate (%" PRIi64 ") sample_rate(%d) channels(%d) fmt(%d) layout(%" PRIi64 ") frame_size(%d)", + audio_out_stream->codecpar->bit_rate, audio_out_stream->codecpar->sample_rate, + audio_out_stream->codecpar->channels, audio_out_stream->codecpar->format, + audio_out_stream->codecpar->channel_layout, audio_out_stream->codecpar->frame_size); + /** Create a new frame to store the audio samples. */ - if ( !(in_frame = zm_av_frame_alloc()) ) { - Error("Could not allocate in frame"); - return false; + if (!in_frame) { + if (!(in_frame = zm_av_frame_alloc())) { + Error("Could not allocate in frame"); + return false; + } } /** Create a new frame to store the audio samples. */ - 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(NULL, + resample_ctx = swr_alloc_set_opts(nullptr, audio_out_ctx->channel_layout, audio_out_ctx->sample_fmt, audio_out_ctx->sample_rate, audio_in_ctx->channel_layout, audio_in_ctx->sample_fmt, audio_in_ctx->sample_rate, - 0, NULL); - if ( !resample_ctx ) { + 0, nullptr); + 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 ) { - Error("Could not open resampler"); + if ((ret = swr_init(resample_ctx)) < 0) { + Error("Could not open resampler %d", ret); av_frame_free(&in_frame); av_frame_free(&out_frame); swr_free(&resample_ctx); 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); - - ret = avresample_open(resample_ctx); - if ( ret < 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; // The codec gives us the frame size, in samples, we calculate the size of the // samples buffer in bytes unsigned int audioSampleBuffer_size = av_samples_get_buffer_size( - NULL, audio_out_ctx->channels, + nullptr, audio_out_ctx->channels, audio_out_ctx->frame_size, 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 { @@ -872,83 +893,263 @@ 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::writeVideoFramePacket(AVPacket *ipkt) { - av_init_packet(&opkt); - - dumpPacket(video_in_stream, ipkt, "video input packet"); - - opkt.flags = ipkt->flags; - opkt.data = ipkt->data; - opkt.size = ipkt->size; - opkt.duration = ipkt->duration; - - // Just because the in stream wraps, doesn't mean the out needs to. - // Really, if we are limiting ourselves to 10min segments I can't imagine every wrapping in the out. - // So need to handle in wrap, without causing out wrap. - // The cameras that Icon has seem to do EOF instead of wrapping - - if ( ipkt->dts != AV_NOPTS_VALUE ) { - if ( !video_first_dts ) { - Debug(2, "Starting video first_dts will become %" PRId64, ipkt->dts); - video_first_dts = ipkt->dts; - } - opkt.dts = ipkt->dts - video_first_dts; - } else { - opkt.dts = next_dts[video_out_stream->index] ? av_rescale_q(next_dts[video_out_stream->index], video_out_stream->time_base, video_in_stream->time_base) : 0; - Debug(3, "Setting dts to video_next_dts %" PRId64 " from %" PRId64, opkt.dts, next_dts[video_out_stream->index]); +int VideoStore::writePacket(const std::shared_ptr &ipkt) { + if (ipkt->codec_type == AVMEDIA_TYPE_VIDEO) { + return writeVideoFramePacket(ipkt); + } else if (ipkt->codec_type == AVMEDIA_TYPE_AUDIO) { + return writeAudioFramePacket(ipkt); } - if ( ipkt->pts != AV_NOPTS_VALUE ) { - opkt.pts = ipkt->pts - video_first_dts; - } else { - opkt.pts = AV_NOPTS_VALUE; - } - av_packet_rescale_ts(&opkt, video_in_stream->time_base, video_out_stream->time_base); - - dumpPacket(video_out_stream, &opkt, "after pts adjustment"); - write_packet(&opkt, video_out_stream); - - zm_av_packet_unref(&opkt); - + Error("Unknown stream type in packet (%d)", ipkt->codec_type); return 0; +} + +int VideoStore::writeVideoFramePacket(const std::shared_ptr &zm_packet) { + frame_count += 1; + + // if we have to transcode + if (monitor->GetOptVideoWriter() == Monitor::ENCODE) { + Debug(3, "Have encoding video frame count (%d)", frame_count); + + if (!zm_packet->out_frame) { + Debug(3, "Have no out frame. codec is %s sw_pf %d %s hw_pf %d %s %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) { + Debug(2, "Have an image, convert it"); + //Go straight to out frame + swscale.Convert( + zm_packet->image, + zm_packet->buffer, + zm_packet->codec_imgsize, + zm_packet->image->AVPixFormat(), + chosen_codec_data->sw_pix_fmt, + video_out_ctx->width, + video_out_ctx->height + ); + } else if (!zm_packet->in_frame) { + Debug(4, "Have no in_frame"); + if (zm_packet->packet.size and !zm_packet->decoded) { + Debug(4, "Decoding"); + if (!zm_packet->decode(video_in_ctx)) { + Debug(2, "unable to decode yet."); + return 0; + } + // Go straight to out frame + swscale.Convert(zm_packet->in_frame, out_frame); + } else { + Error("Have neither in_frame or image in packet %d!", + zm_packet->image_index); + return 0; + } // end if has packet or image + } else { + // Have in_frame.... may need to convert it to out_frame + swscale.Convert(zm_packet->in_frame, zm_packet->out_frame); + } // end if no in_frame + } // end if no out_frame + + AVFrame *frame = zm_packet->out_frame; + +#if HAVE_LIBAVUTIL_HWCONTEXT_H + if (video_out_ctx->hw_frames_ctx) { + 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; + frame->pkt_duration = 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 { + + 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; + + 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; + } + ZM_DUMP_PACKET(opkt, "packet returned by codec"); + + // Need to adjust pts/dts values from codec time to stream time + if (opkt.pts != AV_NOPTS_VALUE) + opkt.pts = av_rescale_q(opkt.pts, video_out_ctx->time_base, video_out_stream->time_base); + if (opkt.dts != AV_NOPTS_VALUE) + opkt.dts = av_rescale_q(opkt.dts, video_out_ctx->time_base, video_out_stream->time_base); + Debug(1, "Timebase conversions using %d/%d -> %d/%d", + video_out_ctx->time_base.num, + video_out_ctx->time_base.den, + video_out_stream->time_base.num, + video_out_stream->time_base.den); + + int64_t duration = 0; + if (zm_packet->in_frame) { + if (zm_packet->in_frame->pkt_duration) { + duration = av_rescale_q( + zm_packet->in_frame->pkt_duration, + video_in_stream->time_base, + video_out_stream->time_base); + Debug(1, "duration from ipkt: pts(%" PRId64 ") = pkt_duration(%" PRId64 ") => (%" PRId64 ") (%d/%d) (%d/%d)", + zm_packet->in_frame->pts, + zm_packet->in_frame->pkt_duration, + duration, + video_in_stream->time_base.num, + video_in_stream->time_base.den, + video_out_stream->time_base.num, + video_out_stream->time_base.den + ); + } else if (video_last_pts != AV_NOPTS_VALUE) { + duration = av_rescale_q( + zm_packet->in_frame->pts - video_last_pts, + video_in_stream->time_base, + video_out_stream->time_base); + Debug(1, "duration calc: pts(%" PRId64 ") - last_pts(%" PRId64 ") = (%" PRId64 ") => (%" PRId64 ")", + zm_packet->in_frame->pts, + video_last_pts, + zm_packet->in_frame->pts - video_last_pts, + duration + ); + if (duration <= 0) { + duration = zm_packet->in_frame->pkt_duration ? + zm_packet->in_frame->pkt_duration : + av_rescale_q(1, video_in_stream->time_base, video_out_stream->time_base); + } + } // end if in_frmae->pkt_duration + video_last_pts = zm_packet->in_frame->pts; + } else { + //duration = av_rescale_q(zm_packet->out_frame->pts - video_last_pts, video_in_stream->time_base, video_out_stream->time_base); + } // end if in_frmae + opkt.duration = duration; + } else { // Passthrough + AVPacket *ipkt = &zm_packet->packet; + ZM_DUMP_STREAM_PACKET(video_in_stream, (*ipkt), "Doing passthrough, just copy packet"); + // Just copy it because the codec is the same + av_init_packet(&opkt); + opkt.data = ipkt->data; + opkt.size = ipkt->size; + opkt.flags = ipkt->flags; + opkt.duration = ipkt->duration; + + if (ipkt->dts != AV_NOPTS_VALUE) { + if (!video_first_dts) { + Debug(2, "Starting video first_dts will become %" PRId64, ipkt->dts); + video_first_dts = ipkt->dts; + } + opkt.dts = ipkt->dts - video_first_dts; + } else { + opkt.dts = next_dts[video_out_stream->index] ? av_rescale_q(next_dts[video_out_stream->index], video_out_stream->time_base, video_in_stream->time_base) : 0; + Debug(3, "Setting dts to video_next_dts %" PRId64 " from %" PRId64, opkt.dts, next_dts[video_out_stream->index]); + } + if (ipkt->pts != AV_NOPTS_VALUE) { + opkt.pts = ipkt->pts - video_first_dts; + } else { + opkt.pts = AV_NOPTS_VALUE; + } + + av_packet_rescale_ts(&opkt, video_in_stream->time_base, video_out_stream->time_base); + ZM_DUMP_STREAM_PACKET(video_out_stream, opkt, "after pts adjustment"); + } // end if codec matches + + write_packet(&opkt, video_out_stream); + zm_av_packet_unref(&opkt); + if (hw_frame) av_frame_free(&hw_frame); + + return 1; } // end int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) -int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { - int 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 + return 0; + // FIXME -ve return codes do not free packet in ffmpeg_camera at the moment } - dumpPacket(audio_in_stream, ipkt, "input packet"); - if ( !audio_first_dts ) { + AVPacket *ipkt = &zm_packet->packet; + int ret; + ZM_DUMP_STREAM_PACKET(audio_in_stream, (*ipkt), "input packet"); + + if (!audio_first_dts) { audio_first_dts = ipkt->dts; audio_next_pts = audio_out_ctx->frame_size; } + Debug(3, "audio first_dts to %" PRId64, audio_first_dts); // Need to adjust pts before feeding to decoder.... should really copy the pkt instead of modifying it - ipkt->pts -= audio_first_dts; - ipkt->dts -= audio_first_dts; - dumpPacket(audio_in_stream, ipkt, "after pts adjustment"); - if ( audio_out_codec ) { + 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; } @@ -956,15 +1157,15 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { 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; @@ -973,7 +1174,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { 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 @@ -984,23 +1185,24 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { 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 = NULL; - } // end while there is data in the resampler - + input_frame = nullptr; + } // end while there is data in the resampler } else { - Debug(2,"copying"); av_init_packet(&opkt); opkt.data = ipkt->data; opkt.size = ipkt->size; opkt.flags = ipkt->flags; opkt.duration = ipkt->duration; - opkt.pts = ipkt->pts; - opkt.dts = ipkt->dts; + opkt.pts = ipkt->pts - audio_first_dts; + opkt.dts = ipkt->dts - audio_first_dts; + + ZM_DUMP_STREAM_PACKET(audio_in_stream, (*ipkt), "after pts adjustment"); av_packet_rescale_ts(&opkt, audio_in_stream->time_base, audio_out_stream->time_base); + ZM_DUMP_STREAM_PACKET(audio_out_stream, opkt, "after stream pts adjustment"); write_packet(&opkt, audio_out_stream); zm_av_packet_unref(&opkt); @@ -1013,15 +1215,15 @@ 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.", @@ -1029,16 +1231,16 @@ int VideoStore::write_packet(AVPacket *pkt, AVStream *stream) { pkt->pts = pkt->dts; } - dumpPacket(stream, pkt, "finished pkt"); - next_dts[stream->index] = opkt.dts + opkt.duration; - Debug(3, "video_next_dts has become %" PRId64, next_dts[stream->index]); + ZM_DUMP_STREAM_PACKET(stream, (*pkt), "finished pkt"); + next_dts[stream->index] = pkt->dts + pkt->duration; + Debug(3, "next_dts for stream %d has become %" PRId64, + stream->index, next_dts[stream->index]); int ret = av_interleaved_write_frame(oc, pkt); - if ( ret != 0 ) { - Error("Error writing packet: %s", - av_make_error_string(ret).c_str()); + if (ret != 0) { + Error("Error writing packet: %s", av_make_error_string(ret).c_str()); } else { - Debug(2, "Success writing packet"); + Debug(4, "Success writing packet"); } return ret; } // end int VideoStore::write_packet(AVPacket *pkt, AVStream *stream) diff --git a/src/zm_videostore.h b/src/zm_videostore.h index 7c465ea4b..c73ed19db 100644 --- a/src/zm_videostore.h +++ b/src/zm_videostore.h @@ -1,97 +1,124 @@ #ifndef ZM_VIDEOSTORE_H #define ZM_VIDEOSTORE_H +#include "zm_config.h" +#include "zm_define.h" #include "zm_ffmpeg.h" +#include "zm_swscale.h" + +#include + extern "C" { -#ifdef HAVE_LIBSWRESAMPLE - #include "libswresample/swresample.h" -#else - #ifdef HAVE_LIBAVRESAMPLE - #include "libavresample/avresample.h" - #endif +#include +#include +#if HAVE_LIBAVUTIL_HWCONTEXT_H +#include #endif -#include "libavutil/audio_fifo.h" } -#if HAVE_LIBAVCODEC - -#include "zm_monitor.h" +class Monitor; +class ZMPacket; +class PacketQueue; class VideoStore { -private: + private: - AVOutputFormat *out_format; - AVFormatContext *oc; - - AVCodec *video_out_codec; - AVCodecContext *video_out_ctx; - AVStream *video_out_stream; - - AVStream *video_in_stream; - - AVStream *audio_in_stream; - - // Move this into the object so that we aren't constantly allocating/deallocating it on the stack - AVPacket opkt; - // we are transcoding - AVFrame *in_frame; - AVFrame *out_frame; - - AVCodecContext *video_in_ctx; - const AVCodec *audio_in_codec; - AVCodecContext *audio_in_ctx; - - // The following are used when encoding the audio stream to AAC - AVStream *audio_out_stream; - AVCodec *audio_out_codec; - AVCodecContext *audio_out_ctx; -#ifdef HAVE_LIBSWRESAMPLE - SwrContext *resample_ctx; -#else -#ifdef HAVE_LIBAVRESAMPLE - AVAudioResampleContext* resample_ctx; + struct CodecData { + const AVCodecID codec_id; + const char *codec_codec; + const char *codec_name; + const enum AVPixelFormat sw_pix_fmt; + const enum AVPixelFormat hw_pix_fmt; +#if HAVE_LIBAVUTIL_HWCONTEXT_H && LIBAVCODEC_VERSION_CHECK(57, 107, 0, 107, 0) + const AVHWDeviceType hwdevice_type; #endif -#endif - AVAudioFifo *fifo; - uint8_t *converted_in_samples; - - const char *filename; - const char *format; - - // These are for in - int64_t video_last_pts; - int64_t video_last_dts; - int64_t audio_last_pts; - int64_t audio_last_dts; + }; - int64_t video_first_pts; - int64_t video_first_dts; - int64_t audio_first_pts; - int64_t audio_first_dts; + static struct CodecData codec_data[]; + CodecData *chosen_codec_data; - // 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; + Monitor *monitor; + AVOutputFormat *out_format; + AVFormatContext *oc; + AVStream *video_out_stream; + AVStream *audio_out_stream; - int max_stream_index; + AVCodecContext *video_in_ctx; + AVCodecContext *video_out_ctx; - bool setup_resampler(); - int write_packet(AVPacket *pkt, AVStream *stream); + AVStream *video_in_stream; + AVStream *audio_in_stream; -public: - VideoStore( - const char *filename_in, - const char *format_in, - AVStream *video_in_stream, - AVStream *audio_in_stream, - Monitor * p_monitor); - bool open(); - ~VideoStore(); + 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; - int writeVideoFramePacket( AVPacket *pkt ); - int writeAudioFramePacket( AVPacket *pkt ); + SWScale swscale; + unsigned int packets_written; + unsigned int frame_count; + + AVBufferRef *hw_device_ctx; + + SwrContext *resample_ctx; + AVAudioFifo *fifo; + uint8_t *converted_in_samples; + + const char *filename; + const char *format; + + // 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; + + // 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; + + 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 1e0a16cda..29511f6bd 100644 --- a/src/zm_zone.cpp +++ b/src/zm_zone.cpp @@ -17,20 +17,13 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#define __STDC_FORMAT_MACROS 1 -#include -#include "zm.h" -#include "zm_db.h" #include "zm_zone.h" -#include "zm_image.h" -#include "zm_monitor.h" + #include "zm_fifo.h" +#include "zm_fifo_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, @@ -39,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, @@ -49,11 +42,6 @@ void Zone::Setup( int p_overload_frames, int p_extend_alarm_frames ) { - monitor = p_monitor; - - id = p_id; - label = new char[strlen(p_label)+1]; - strcpy( label, p_label ); type = p_type; polygon = p_polygon; alarm_rgb = p_alarm_rgb; @@ -72,27 +60,21 @@ 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; - image = 0; - score = 0; + ResetStats(); + image = nullptr; overload_count = 0; extend_alarm_count = 0; - pg_image = new Image( monitor->Width(), monitor->Height(), 1, ZM_SUBPIX_ORDER_NONE ); + pg_image = new Image(monitor->Width(), monitor->Height(), 1, ZM_SUBPIX_ORDER_NONE); pg_image->Clear(); - pg_image->Fill( 0xff, polygon ); - pg_image->Outline( 0xff, polygon ); + pg_image->Fill(0xff, polygon); + pg_image->Outline(0xff, polygon); ranges = new Range[monitor->Height()]; for ( unsigned int y = 0; y < monitor->Height(); y++ ) { @@ -112,35 +94,50 @@ void Zone::Setup( } } - if ( config.record_diag_images ) { - snprintf(diag_path, sizeof(diag_path), config.record_diag_images_fifo ? "%s/diagpipe-%d-poly.jpg" : "%s/diag-%d-poly.jpg", monitor->getStorage()->Path(), id); - if (config.record_diag_images_fifo) - FifoStream::fifo_create_if_missing(diag_path); + if (config.record_diag_images) { + if (config.record_diag_images_fifo) { + diag_path = stringtf("%s/diagpipe-%d-poly.jpg", + staticConfig.PATH_SOCKS.c_str(), id); + + Fifo::fifo_create_if_missing(diag_path.c_str()); + } else { + diag_path = stringtf("%s/diag-%d-poly.jpg", + monitor->getStorage()->Path(), id); + } + pg_image->WriteJpeg(diag_path, config.record_diag_images_fifo); - } else { - diag_path[0] = 0; } -} // end Zone::Setup +} // end Zone::Setup Zone::~Zone() { - delete[] label; - delete image; + if (image) + delete image; delete pg_image; delete[] ranges; } -void Zone::RecordStats( const Event *event ) { - static char sql[ZM_SQL_MED_BUFSIZ]; - db_mutex.lock(); - snprintf(sql, sizeof(sql), - "INSERT INTO Stats SET MonitorId=%d, ZoneId=%d, EventId=%" PRIu64 ", FrameId=%d, PixelDiff=%d, AlarmPixels=%d, FilterPixels=%d, BlobPixels=%d, Blobs=%d, MinBlobSize=%d, MaxBlobSize=%d, MinX=%d, MinY=%d, MaxX=%d, MaxY=%d, Score=%d", - monitor->Id(), id, event->Id(), event->Frames()+1, 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 +void Zone::RecordStats(const Event *event) { + 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_ ); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't insert event stats: %s", mysql_error(&dbconn)); - } - db_mutex.unlock(); -} // end void Zone::RecordStats( const Event *event ) + zmDbDo(sql); +} // end void Zone::RecordStats( const Event *event ) bool Zone::CheckOverloadCount() { if ( overload_count ) { @@ -149,40 +146,41 @@ bool Zone::CheckOverloadCount() { return false; } return true; -} // end bool Zone::CheckOverloadCount() +} // end bool Zone::CheckOverloadCount() void Zone::SetScore(unsigned int nScore) { - score = nScore; -} // end void Zone::SetScore(unsigned int nScore) + stats.score_ = nScore; +} // end void Zone::SetScore(unsigned int nScore) void Zone::SetAlarmImage(const Image* srcImage) { - delete image; + if ( image ) + delete image; image = new Image(*srcImage); -} // end void Zone::SetAlarmImage( const Image* srcImage ) +} // end void Zone::SetAlarmImage( const Image* srcImage ) int Zone::GetOverloadCount() { return overload_count; -} // end int Zone::GetOverloadCount() +} // end int Zone::GetOverloadCount() void Zone::SetOverloadCount(int nOverCount) { overload_count = nOverCount; -} // end void Zone::SetOverloadCount(int nOverCount ) +} // end void Zone::SetOverloadCount(int nOverCount ) int Zone::GetOverloadFrames() { return overload_frames; -} // end int Zone::GetOverloadFrames +} // end int Zone::GetOverloadFrames int Zone::GetExtendAlarmCount() { return extend_alarm_count; -} // end int Zone::GetExtendAlarmCount() +} // end int Zone::GetExtendAlarmCount() void Zone::SetExtendAlarmCount(int nExtendAlarmCount) { extend_alarm_count = nExtendAlarmCount; -} // end void Zone::SetExtendAlarmCount( int nExtendAlarmCount ) +} // end void Zone::SetExtendAlarmCount( int nExtendAlarmCount ) int Zone::GetExtendAlarmFrames() { return extend_alarm_frames; -} // end int Zone::GetExtendAlarmFrames() +} // end int Zone::GetExtendAlarmFrames() bool Zone::CheckExtendAlarmCount() { Info("ExtendAlarm count: %d, ExtendAlarm frames: %d", extend_alarm_count, extend_alarm_frames); @@ -192,22 +190,23 @@ bool Zone::CheckExtendAlarmCount() { return true; } return false; -} // end bool Zone::CheckExtendAlarmCount +} // end bool Zone::CheckExtendAlarmCount bool Zone::CheckAlarms(const Image *delta_image) { ResetStats(); - if ( overload_count ) { + if (overload_count) { Info("In overload mode, %d frames of %d remaining", overload_count, overload_frames); overload_count--; return false; } - delete image; + if (image) + delete image; // Get the difference image Image *diff_image = image = new Image(*delta_image); int diff_width = diff_image->Width(); - 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; @@ -220,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 && sseversion >= 20) { + /* 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; @@ -262,146 +262,137 @@ bool Zone::CheckAlarms(const Image *delta_image) { return false; } - if ( max_alarm_pixels != 0 ) - score = (100*alarm_pixels)/max_alarm_pixels; - else - score = (100*alarm_pixels)/polygon.Area(); - - if ( score < 1 ) - score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ - Debug(5, "Current score is %d", score); + stats.score_ = (100*stats.alarm_pixels_)/(max_alarm_pixels ? max_alarm_pixels : polygon.Area()); + if (stats.score_ < 1) + stats.score_ = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ + Debug(5, "Current score is %d", stats.score_); - if ( check_method >= FILTERED_PIXELS ) { - 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 == WHITE ) { + for (int x = lo_x; x <= hi_x; x++, pdiff++) { + if (*pdiff == kWhite) { // Check participation in an X block ldx = (x>=(lo_x+bx1))?-bx1:lo_x-x; hdx = (x<=(hi_x-bx1))?0:((hi_x-x)-bx1); ldy = (y>=(lo_y+by1))?-by1:lo_y-y; hdy = (y<=(hi_y-by1))?0:((hi_y-y)-by1); block = false; - for ( int dy = ldy; !block && dy <= hdy; dy++ ) { - for ( int dx = ldx; !block && dx <= hdx; dx++ ) { + for (int dy = ldy; !block && dy <= hdy; dy++) { + for (int dx = ldx; !block && dx <= hdx; dx++) { block = true; - for ( int dy2 = 0; block && dy2 < by; dy2++ ) { - for ( int dx2 = 0; block && dx2 < bx; dx2++ ) { + for (int dy2 = 0; block && dy2 < by; dy2++) { + for (int dx2 = 0; block && dx2 < bx; dx2++) { cpdiff = diff_buff + (((y+dy+dy2)*diff_width) + (x+dx+dx2)); - if ( !*cpdiff ) { + if (!*cpdiff ) { block = false; } } } } } - if ( !block ) { - *pdiff = BLACK; + if (!block) { + *pdiff = kBlack; continue; } - alarm_filter_pixels++; - } // end if white - } // end for x - } // end foreach y line + stats.alarm_filter_pixels_++; + } // end if white + } // end for x + } // end foreach y line } else { - alarm_filter_pixels = alarm_pixels; + stats.alarm_filter_pixels_ = stats.alarm_pixels_; } - if ( config.record_diag_images ) + 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"); - typedef struct { unsigned char tag; int count; int lo_x; int hi_x; int lo_y; int hi_y; } BlobStats; - BlobStats blob_stats[256]; + // ICON FIXME Would like to get rid of this memset memset(blob_stats, 0, sizeof(BlobStats)*256); uint8_t *spdiff; uint8_t last_x, last_y; BlobStats *bsx, *bsy; BlobStats *bsm, *bss; - for ( unsigned int y = lo_y; y <= hi_y; y++ ) { + for (unsigned int y = lo_y; y <= hi_y; y++) { int lo_x = ranges[y].lo_x; int hi_x = ranges[y].hi_x; - pdiff = (uint8_t*)diff_image->Buffer( lo_x, y ); - for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) { - if ( *pdiff == WHITE ) { + pdiff = diff_image->Buffer(lo_x, y); + for (int x = lo_x; x <= hi_x; x++, pdiff++) { + if (*pdiff == kWhite) { Debug(9, "Got white pixel at %d,%d (%p)", x, y, pdiff); - //last_x = (x>lo_x)?*(pdiff-1):0; - //last_y = (y>lo_y&&x>=last_lo_x&&x<=last_hi_x)?*(pdiff-diff_width):0; - - last_x = 0; - if ( x > 0 ) { - if ( (x-1) >= lo_x ) { - last_x = *(pdiff-1); - } - } + last_x = ((x > 0) && ( (x-1) >= lo_x )) ? *(pdiff-1) : 0; last_y = 0; - if (y > 0 ) { + if ( y > 0 ) { if ( (y-1) >= lo_y && ranges[(y-1)].lo_x <= x && ranges[(y-1)].hi_x >= x ) { last_y = *(pdiff-diff_width); } } - if ( last_x ) { + if (last_x) { Debug(9, "Left neighbour is %d", last_x); bsx = &blob_stats[last_x]; - if ( last_y ) { + if (last_y) { Debug(9, "Top neighbour is %d", last_y); bsy = &blob_stats[last_y]; - if ( last_x == last_y ) { + if (last_x == last_y) { Debug(9, "Matching neighbours, setting to %d", last_x); // Add to the blob from the x side (either side really) *pdiff = last_x; - alarm_blob_pixels++; + stats.alarm_blob_pixels_++; bsx->count++; - if ( x > bsx->hi_x ) bsx->hi_x = x; - if ( (int)y > bsx->hi_y ) bsx->hi_y = y; + if (x > bsx->hi_x) bsx->hi_x = x; + if ((int)y > bsx->hi_y) bsx->hi_y = y; } else { // Aggregate blobs bsm = bsx->count>=bsy->count?bsx:bsy; @@ -417,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; @@ -426,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", @@ -450,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; @@ -474,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 = (WHITE-1); i > 0; i-- ) { + for (i = (kWhite-1); i > 0; i--) { BlobStats *bs = &blob_stats[i]; // See if we can recycle one first, only if it's at least two rows up - if ( bs->count && bs->hi_y < (int)(y-1) ) { + if (bs->count && bs->hi_y < (int)(y-1)) { if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) { - if ( config.create_analysis_images || config.record_diag_images ) { - for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) { + if (( monitor->GetOptSaveJPEGs() > 1 ) || config.record_diag_images) { + for (int sy = bs->lo_y; sy <= bs->hi_y; sy++) { spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); - for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) { - if ( *spdiff == bs->tag ) { - *spdiff = BLACK; + for (int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++) { + if (*spdiff == bs->tag) { + *spdiff = kBlack; } } } } - alarm_blobs--; - alarm_blob_pixels -= bs->count; + stats.alarm_blobs_--; + stats.alarm_blob_pixels_ -= bs->count; Debug(6, "Eliminated blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", - i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs); + i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, stats.alarm_blobs_); bs->tag = 0; bs->count = 0; @@ -527,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; @@ -552,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 ( int i = 1; i < WHITE; i++ ) { + for (uint32 i = 1; i < kWhite; i++) { BlobStats *bs = &blob_stats[i]; - if ( bs->count ) { - if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) { - if ( config.create_analysis_images || config.record_diag_images ) { - for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) { + if (bs->count) { + if ((min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels)) { + if (( monitor->GetOptSaveJPEGs() > 1 ) || config.record_diag_images) { + for (int sy = bs->lo_y; sy <= bs->hi_y; sy++) { spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); - for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) { - if ( *spdiff == bs->tag ) { - *spdiff = BLACK; + for (int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++) { + if (*spdiff == bs->tag) { + *spdiff = kBlack; } } } } - alarm_blobs--; - alarm_blob_pixels -= bs->count; + stats.alarm_blobs_--; + stats.alarm_blob_pixels_ -= bs->count; Debug(6, "Eliminated blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", - i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs); + i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, stats.alarm_blobs_); bs->tag = 0; bs->count = 0; @@ -596,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 ( int i = 1; i < WHITE; 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 { @@ -667,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 { @@ -679,64 +676,65 @@ 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) && config.create_analysis_images ) { + if ((type < PRECLUSIVE) && (check_method >= BLOBS) && (monitor->GetOptSaveJPEGs() > 1)) { + unsigned int lo_x = polygon.Extent().Lo().x_; // First mask out anything we don't want - for ( unsigned int y = lo_y; y <= hi_y; y++ ) { + for (unsigned int y = lo_y; y <= hi_y; y++) { pdiff = diff_buff + ((diff_width * y) + lo_x); int lo_x2 = ranges[y].lo_x; int hi_x2 = ranges[y].hi_x; int lo_gap = lo_x2-lo_x; - if ( lo_gap > 0 ) { - if ( lo_gap == 1 ) { - *pdiff++ = BLACK; + if (lo_gap > 0) { + if (lo_gap == 1) { + *pdiff++ = kBlack; } else { - memset(pdiff, BLACK, lo_gap); + memset(pdiff, kBlack, lo_gap); pdiff += lo_gap; } } const uint8_t* ppoly = pg_image->Buffer(lo_x2, y); - for ( int x = lo_x2; x <= hi_x2; x++, pdiff++, ppoly++ ) { - if ( !*ppoly ) { - *pdiff = BLACK; + for (int x = lo_x2; x <= hi_x2; x++, pdiff++, ppoly++) { + if (!*ppoly) { + *pdiff = kBlack; } } int hi_gap = hi_x-hi_x2; - if ( hi_gap > 0 ) { - if ( hi_gap == 1 ) { - *pdiff = BLACK; + if (hi_gap > 0) { + if (hi_gap == 1) { + *pdiff = kBlack; } else { - memset(pdiff, BLACK, hi_gap); + memset(pdiff, kBlack, hi_gap); } } - } // end for y + } // end for y - if ( monitor->Colours() == ZM_COLOUR_GRAY8 ) { + if (monitor->Colours() == ZM_COLOUR_GRAY8) { image = diff_image->HighlightEdges(alarm_rgb, ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB, &polygon.Extent()); } else { image = diff_image->HighlightEdges(alarm_rgb, monitor->Colours(), monitor->SubpixelOrder(), &polygon.Extent()); @@ -744,79 +742,51 @@ bool Zone::CheckAlarms(const Image *delta_image) { // Only need to delete this when 'image' becomes detached and points somewhere else delete diff_image; - } else { - delete image; - image = 0; - } + diff_image = nullptr; + } // end if ( (type < PRECLUSIVE) && (check_method >= BLOBS) && (monitor->GetOptSaveJPEGs() > 1) Debug(1, "%s: Pixel Diff: %d, Alarm Pixels: %d, Filter Pixels: %d, Blob Pixels: %d, Blobs: %d, Score: %d", - Label(), pixel_diff, alarm_pixels, alarm_filter_pixels, alarm_blob_pixels, alarm_blobs, score); - } + Label(), stats.pixel_diff_, stats.alarm_pixels_, stats.alarm_filter_pixels_, stats.alarm_blob_pixels_, stats.alarm_blobs_, stats.score_); + } // end if score return true; } bool Zone::ParsePolygonString(const char *poly_string, Polygon &polygon) { - Debug(3, "Parsing polygon string '%s'", poly_string); - - char *str_ptr = new char[strlen(poly_string)+1]; - char *str = str_ptr; - strcpy(str, poly_string); - - char *ws; - int n_coords = 0; + char *str = (char *)poly_string; int max_n_coords = strlen(str)/4; - Coord *coords = new Coord[max_n_coords]; - while( true ) { - if ( *str == '\0' ) { - break; - } - ws = strchr(str, ' '); - if ( ws ) { - *ws = '\0'; - } + + std::vector vertices; + vertices.reserve(max_n_coords); + + while (*str != '\0') { char *cp = strchr(str, ','); - if ( !cp ) { + if (!cp) { Error("Bogus coordinate %s found in polygon string", str); - delete[] coords; - delete[] str_ptr; - return false; - } else { - *cp = '\0'; - char *xp = str; - char *yp = cp+1; - - int x = atoi(xp); - int y = atoi(yp); - - Debug(3, "Got coordinate %d,%d from polygon string", x, y); -#if 0 - if ( x < 0 ) - x = 0; - else if ( x >= width ) - x = width-1; - if ( y < 0 ) - y = 0; - else if ( y >= height ) - y = height-1; -#endif - coords[n_coords++] = Coord( x, y ); - } - if ( ws ) - str = ws+1; - else break; + } + + int x = atoi(str); + int y = atoi(cp + 1); + Debug(3, "Got coordinate %d,%d from polygon string", x, y); + vertices.emplace_back(x, y); + + char *ws = strchr(cp + 2, ' '); + if (ws) { + str = ws + 1; + } else { + break; + } } - polygon = Polygon(n_coords, coords); - Debug(3, "Successfully parsed polygon string"); - //printf( "Area: %d\n", pg.Area() ); - //printf( "Centre: %d,%d\n", pg.Centre().X(), pg.Centre().Y() ); + if (vertices.size() > 2) { + Debug(3, "Successfully parsed polygon string %s", str); + polygon = Polygon(vertices); + } else { + Error("Not enough coordinates to form a polygon!"); + } - delete[] coords; - delete[] str_ptr; - - return true; -} + 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) { Debug(3, "Parsing zone string '%s'", zone_string); @@ -825,13 +795,12 @@ bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, P char *str = str_ptr; strcpy(str, zone_string); + zone_id = strtol(str, 0, 10); + Debug(3, "Got zone %d from zone string", zone_id); + char *ws = strchr(str, ' '); if ( !ws ) { Debug(3, "No initial whitespace found in zone string '%s', finishing", str); - } - zone_id = strtol(str, 0, 10); - Debug(3, "Got zone %d from zone string", zone_id); - if ( !ws ) { delete[] str_ptr; return true; } @@ -839,13 +808,11 @@ bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, P *ws = '\0'; str = ws+1; + colour = strtol(str, 0, 16); + Debug(3, "Got colour %06x from zone string", colour); ws = strchr(str, ' '); if ( !ws ) { Debug(3, "No secondary whitespace found in zone string '%s', finishing", zone_string); - } - colour = strtol(str, 0, 16); - Debug(3, "Got colour %06x from zone string", colour); - if ( !ws ) { delete[] str_ptr; return true; } @@ -853,47 +820,40 @@ bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, P str = ws+1; bool result = ParsePolygonString(str, polygon); - - //printf( "Area: %d\n", pg.Area() ); - //printf( "Centre: %d,%d\n", pg.Centre().X(), pg.Centre().Y() ); - delete[] str_ptr; return result; -} +} // end bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, Polygon &polygon) -int Zone::Load(Monitor *monitor, Zone **&zones) { - static char sql[ZM_SQL_MED_BUFSIZ]; +std::vector Zone::Load(Monitor *monitor) { + std::vector zones; - db_mutex.lock(); - snprintf(sql, sizeof(sql), "select Id,Name,Type+0,Units,Coords,AlarmRGB,CheckMethod+0,MinPixelThreshold,MaxPixelThreshold,MinAlarmPixels,MaxAlarmPixels,FilterX,FilterY,MinFilterPixels,MaxFilterPixels,MinBlobPixels,MaxBlobPixels,MinBlobs,MaxBlobs,OverloadFrames,ExtendAlarmFrames from Zones where MonitorId = %d order by Type, Id", monitor->Id()); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - db_mutex.unlock(); - return 0; + std::string sql = stringtf("SELECT Id,Name,Type+0,Units,Coords,AlarmRGB,CheckMethod+0," + "MinPixelThreshold,MaxPixelThreshold,MinAlarmPixels,MaxAlarmPixels," + "FilterX,FilterY,MinFilterPixels,MaxFilterPixels," + "MinBlobPixels,MaxBlobPixels,MinBlobs,MaxBlobs," + "OverloadFrames,ExtendAlarmFrames" + " FROM Zones WHERE MonitorId = %d ORDER BY Type, Id", monitor->Id()); + + MYSQL_RES *result = zmDbFetch(sql); + if (!result) { + return {}; } - MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - db_mutex.unlock(); - return 0; - } - db_mutex.unlock(); - int n_zones = mysql_num_rows(result); + uint32 n_zones = mysql_num_rows(result); Debug(1, "Got %d zones for monitor %s", n_zones, monitor->Name()); - delete[] zones; - zones = new Zone *[n_zones]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { + + zones.reserve(n_zones); + for (int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++) { int col = 0; int Id = atoi(dbrow[col++]); const char *Name = dbrow[col++]; - int Type = atoi(dbrow[col++]); + ZoneType Type = static_cast(atoi(dbrow[col++])); const char *Units = dbrow[col++]; const char *Coords = dbrow[col++]; int AlarmRGB = dbrow[col]?atoi(dbrow[col]):0; col++; - int CheckMethod = atoi(dbrow[col++]); + Zone::CheckMethod CheckMethod = static_cast(atoi(dbrow[col++])); int MinPixelThreshold = dbrow[col]?atoi(dbrow[col]):0; col++; int MaxPixelThreshold = dbrow[col]?atoi(dbrow[col]):0; col++; int MinAlarmPixels = dbrow[col]?atoi(dbrow[col]):0; col++; @@ -916,16 +876,31 @@ int Zone::Load(Monitor *monitor, Zone **&zones) { Polygon polygon; if ( !ParsePolygonString(Coords, polygon) ) { Error("Unable to parse polygon string '%s' for zone %d/%s for monitor %s, ignoring", Coords, Id, Name, monitor->Name()); - n_zones -= 1; continue; } - if ( polygon.LoX() < 0 || polygon.HiX() >= (int)monitor->Width() - || polygon.LoY() < 0 || polygon.HiY() >= (int)monitor->Height() ) { - Error("Zone %d/%s for monitor %s extends outside of image dimensions, (%d,%d), (%d,%d), ignoring", - Id, Name, monitor->Name(), polygon.LoX(), polygon.LoY(), polygon.HiX(), polygon.HiY()); - n_zones -= 1; - continue; + 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) != (%d,%d), fixing", + Id, + Name, + monitor->Name(), + polygon.Extent().Lo().x_, + polygon.Extent().Lo().y_, + polygon.Extent().Hi().x_, + polygon.Extent().Hi().y_, + monitor->Width(), + monitor->Height()); + + polygon.Clip(Box( + {0, 0}, + {static_cast(monitor->Width()), static_cast(monitor->Height())} + )); } if ( false && !strcmp( Units, "Percent" ) ) { @@ -937,23 +912,30 @@ int Zone::Load(Monitor *monitor, Zone **&zones) { MaxBlobPixels = (MaxBlobPixels*polygon.Area())/100; } - if ( atoi(dbrow[2]) == Zone::INACTIVE ) { - zones[i] = new Zone(monitor, Id, Name, polygon); - } else if ( atoi(dbrow[2]) == Zone::PRIVACY ) { - zones[i] = new Zone(monitor, Id, Name, (Zone::ZoneType)Type, polygon); + if (atoi(dbrow[2]) == Zone::INACTIVE) { + zones.emplace_back(monitor, Id, Name, polygon); + } else if (atoi(dbrow[2]) == Zone::PRIVACY) { + zones.emplace_back(monitor, Id, Name, Type, polygon); + } else { + zones.emplace_back( + monitor, Id, Name, Type, polygon, AlarmRGB, + CheckMethod, MinPixelThreshold, MaxPixelThreshold, + MinAlarmPixels, MaxAlarmPixels, Vector2(FilterX, FilterY), + MinFilterPixels, MaxFilterPixels, + MinBlobPixels, MaxBlobPixels, MinBlobs, MaxBlobs, + OverloadFrames, ExtendAlarmFrames); } - zones[i] = new Zone(monitor, Id, Name, (Zone::ZoneType)Type, polygon, AlarmRGB, (Zone::CheckMethod)CheckMethod, MinPixelThreshold, MaxPixelThreshold, MinAlarmPixels, MaxAlarmPixels, Coord( FilterX, FilterY ), MinFilterPixels, MaxFilterPixels, MinBlobPixels, MaxBlobPixels, MinBlobs, MaxBlobs, OverloadFrames, ExtendAlarmFrames); } // end foreach row mysql_free_result(result); - return n_zones; -} // end int Zone::Load(Monitor *monitor, Zone **&zones) + return zones; +} // end std::vector Zone::Load(Monitor *monitor) -bool Zone::DumpSettings(char *output, bool /*verbose*/) { +bool Zone::DumpSettings(char *output, bool /*verbose*/) const { output[0] = 0; - sprintf( output+strlen(output), " Id : %d\n", id ); - sprintf( output+strlen(output), " Label : %s\n", label ); - sprintf( output+strlen(output), " Type: %d - %s\n", type, + sprintf(output+strlen(output), " Id : %d\n", id ); + sprintf(output+strlen(output), " Label : %s\n", label.c_str() ); + sprintf(output+strlen(output), " Type: %d - %s\n", type, type==ACTIVE?"Active":( type==INCLUSIVE?"Inclusive":( type==EXCLUSIVE?"Exclusive":( @@ -961,9 +943,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, @@ -975,17 +957,21 @@ 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 ); sprintf( output+strlen(output), " Max Blob Pixels : %d\n", max_blob_pixels ); sprintf( output+strlen(output), " Min Blobs : %d\n", min_blobs ); sprintf( output+strlen(output), " Max Blobs : %d\n", max_blobs ); - return( true ); + return true; } -void Zone::std_alarmedpixels(Image* pdiff_image, const Image* ppoly_image, unsigned int* pixel_count, unsigned int* pixel_sum) { +void Zone::std_alarmedpixels( + Image* pdiff_image, + const Image* ppoly_image, + unsigned int* pixel_count, + unsigned int* pixel_sum) { uint32_t pixelsalarmed = 0; uint32_t pixelsdifference = 0; uint8_t calc_max_pixel_threshold = 255; @@ -995,29 +981,67 @@ void Zone::std_alarmedpixels(Image* pdiff_image, const Image* ppoly_image, unsig 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++ ) { if ( *ppoly && (*pdiff > min_pixel_threshold) && (*pdiff <= calc_max_pixel_threshold) ) { pixelsalarmed++; pixelsdifference += *pdiff; - *pdiff = WHITE; + *pdiff = kWhite; } else { - *pdiff = BLACK; + *pdiff = kBlack; } } - } + } // end for y = lo_y to hi_y /* Store the results */ *pixel_count = pixelsalarmed; *pixel_sum = pixelsdifference; Debug(7, "STORED pixelsalarmed(%d), pixelsdifference(%d)", pixelsalarmed, pixelsdifference); +} // end void Zone::std_alarmedpixels(Image* pdiff_image, const Image* ppoly_image, unsigned int* pixel_count, unsigned int* pixel_sum) + +Zone::Zone(const Zone &z) : + monitor(z.monitor), + id(z.id), + label(z.label), + type(z.type), + polygon(z.polygon), + alarm_rgb(z.alarm_rgb), + check_method(z.check_method), + min_pixel_threshold(z.min_pixel_threshold), + max_pixel_threshold(z.max_pixel_threshold), + min_alarm_pixels(z.min_alarm_pixels), + max_alarm_pixels(z.max_alarm_pixels), + filter_box(z.filter_box), + min_filter_pixels(z.min_filter_pixels), + max_filter_pixels(z.max_filter_pixels), + min_blob_pixels(z.min_blob_pixels), + max_blob_pixels(z.max_blob_pixels), + min_blobs(z.min_blobs), + max_blobs(z.max_blobs), + overload_frames(z.overload_frames), + extend_alarm_frames(z.extend_alarm_frames), + alarmed(z.alarmed), + was_alarmed(z.was_alarmed), + stats(z.stats), + overload_count(z.overload_count), + extend_alarm_count(z.extend_alarm_count), + diag_path(z.diag_path) +{ + std::copy(z.blob_stats, z.blob_stats+256, blob_stats); + pg_image = z.pg_image ? new Image(*z.pg_image) : nullptr; + ranges = new Range[monitor->Height()]; + std::copy(z.ranges, z.ranges+monitor->Height(), ranges); + image = z.image ? new Image(*z.image) : nullptr; + //z.stats.debug("Copy Source"); + stats.DumpToLog("Copy dest"); } + diff --git a/src/zm_zone.h b/src/zm_zone.h index 0c7b26a57..741b61ae2 100644 --- a/src/zm_zone.h +++ b/src/zm_zone.h @@ -20,160 +20,216 @@ #ifndef ZM_ZONE_H #define ZM_ZONE_H -#include "zm_rgb.h" -#include "zm_coord.h" +#include "zm_box.h" +#include "zm_define.h" +#include "zm_config.h" #include "zm_poly.h" -#include "zm_image.h" -#include "zm_event.h" +#include "zm_rgb.h" +#include "zm_zone_stats.h" +#include "zm_vector2.h" +#include +#include +#include + +class Event; +class Image; class Monitor; // // This describes a 'zone', or an area of an image that has certain // detection characteristics. // -class Zone -{ -protected: - struct Range - { - int lo_x; - int hi_x; - int off_x; - }; -public: - typedef enum { ACTIVE=1, INCLUSIVE, EXCLUSIVE, PRECLUSIVE, INACTIVE, PRIVACY } ZoneType; - typedef enum { ALARMED_PIXELS=1, FILTERED_PIXELS, BLOBS } CheckMethod; +class Zone { + protected: + struct Range { + int lo_x; + int hi_x; + int off_x; + }; + typedef struct { + unsigned char tag; + int count; + int lo_x; + int hi_x; + int lo_y; + int hi_y; + } BlobStats; + public: + typedef enum { ACTIVE=1, INCLUSIVE, EXCLUSIVE, PRECLUSIVE, INACTIVE, PRIVACY } ZoneType; + typedef enum { ALARMED_PIXELS=1, FILTERED_PIXELS, BLOBS } CheckMethod; -protected: - // Inputs - Monitor *monitor; + protected: + // Inputs + Monitor *monitor; - int id; - char *label; - ZoneType type; - Polygon polygon; - Rgb alarm_rgb; - CheckMethod check_method; + int id; + std::string label; + ZoneType type; + Polygon polygon; + Rgb alarm_rgb; + CheckMethod check_method; - int min_pixel_threshold; - int max_pixel_threshold; + int min_pixel_threshold; + int max_pixel_threshold; - int min_alarm_pixels; - int max_alarm_pixels; + int min_alarm_pixels; + int max_alarm_pixels; - Coord filter_box; - int min_filter_pixels; - int max_filter_pixels; + Vector2 filter_box; + int min_filter_pixels; + int max_filter_pixels; - int min_blob_pixels; - int max_blob_pixels; - int min_blobs; - int max_blobs; + BlobStats blob_stats[256]; + int min_blob_pixels; + int max_blob_pixels; + int min_blobs; + int max_blobs; - int overload_frames; - int extend_alarm_frames; + int overload_frames; + int extend_alarm_frames; - // Outputs/Statistics - bool alarmed; - bool was_alarmed; - int pixel_diff; - unsigned int alarm_pixels; - int alarm_filter_pixels; - int alarm_blob_pixels; - int alarm_blobs; - int min_blob_size; - int max_blob_size; - Box alarm_box; - Coord alarm_centre; - unsigned int score; - Image *pg_image; - Range *ranges; - Image *image; + // Outputs/Statistics + bool alarmed; + bool was_alarmed; + ZoneStats stats; + Image *pg_image; + Range *ranges; + Image *image; - int overload_count; - int extend_alarm_count; - char diag_path[PATH_MAX]; + int overload_count; + int extend_alarm_count; + std::string diag_path; -protected: - void Setup( Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_type, const Polygon &p_polygon, const Rgb p_alarm_rgb, CheckMethod p_check_method, int p_min_pixel_threshold, int p_max_pixel_threshold, int p_min_alarm_pixels, int p_max_alarm_pixels, const Coord &p_filter_box, int p_min_filter_pixels, int p_max_filter_pixels, int p_min_blob_pixels, int p_max_blob_pixels, int p_min_blobs, int p_max_blobs, int p_overload_frames, int p_extend_alarm_frames ); - void std_alarmedpixels(Image* pdiff_image, const Image* ppoly_image, unsigned int* pixel_count, unsigned int* pixel_sum); - -public: - Zone( Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_type, const Polygon &p_polygon, const Rgb p_alarm_rgb, CheckMethod p_check_method, int p_min_pixel_threshold=15, int p_max_pixel_threshold=0, int p_min_alarm_pixels=50, int p_max_alarm_pixels=75000, const Coord &p_filter_box=Coord( 3, 3 ), int p_min_filter_pixels=50, int p_max_filter_pixels=50000, int p_min_blob_pixels=10, int p_max_blob_pixels=0, int p_min_blobs=0, int p_max_blobs=0, int p_overload_frames=0, int p_extend_alarm_frames=0 ) - { - Setup( p_monitor, p_id, p_label, p_type, p_polygon, p_alarm_rgb, p_check_method, p_min_pixel_threshold, p_max_pixel_threshold, p_min_alarm_pixels, p_max_alarm_pixels, p_filter_box, p_min_filter_pixels, p_max_filter_pixels, p_min_blob_pixels, p_max_blob_pixels, p_min_blobs, p_max_blobs, p_overload_frames, p_extend_alarm_frames ); - } - Zone( Monitor *p_monitor, int p_id, const char *p_label, const Polygon &p_polygon, const Rgb p_alarm_rgb, CheckMethod p_check_method, int p_min_pixel_threshold=15, int p_max_pixel_threshold=0, int p_min_alarm_pixels=50, int p_max_alarm_pixels=75000, const Coord &p_filter_box=Coord( 3, 3 ), int p_min_filter_pixels=50, int p_max_filter_pixels=50000, int p_min_blob_pixels=10, int p_max_blob_pixels=0, int p_min_blobs=0, int p_max_blobs=0, int p_overload_frames=0, int p_extend_alarm_frames=0) - { - Setup( p_monitor, p_id, p_label, Zone::ACTIVE, p_polygon, p_alarm_rgb, p_check_method, p_min_pixel_threshold, p_max_pixel_threshold, p_min_alarm_pixels, p_max_alarm_pixels, p_filter_box, p_min_filter_pixels, p_max_filter_pixels, p_min_blob_pixels, p_max_blob_pixels, p_min_blobs, p_max_blobs, p_overload_frames, p_extend_alarm_frames ); - } - Zone( Monitor *p_monitor, int p_id, const char *p_label, const Polygon &p_polygon ) - { - Setup( p_monitor, p_id, p_label, Zone::INACTIVE, p_polygon, RGB_BLACK, (Zone::CheckMethod)0, 0, 0, 0, 0, Coord( 0, 0 ), 0, 0, 0, 0, 0, 0, 0, 0 ); - } - Zone( Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_type, const Polygon &p_polygon ) - { - Setup( p_monitor, p_id, p_label, p_type, p_polygon, RGB_BLACK, (Zone::CheckMethod)0, 0, 0, 0, 0, Coord( 0, 0 ), 0, 0, 0, 0, 0, 0, 0, 0 ); - } + protected: + void Setup( + ZoneType p_type, + const Polygon &p_polygon, + const Rgb p_alarm_rgb, + CheckMethod p_check_method, + int p_min_pixel_threshold, + int p_max_pixel_threshold, + int p_min_alarm_pixels, + int p_max_alarm_pixels, + const 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); -public: - ~Zone(); + void std_alarmedpixels(Image* pdiff_image, const Image* ppoly_image, unsigned int* pixel_count, unsigned int* pixel_sum); - inline int Id() const { return( id ); } - inline const char *Label() const { return( label ); } - inline ZoneType Type() const { return( type ); } - inline bool IsActive() const { return( type == ACTIVE ); } - inline bool IsInclusive() const { return( type == INCLUSIVE ); } - inline bool IsExclusive() const { return( type == EXCLUSIVE ); } - inline bool IsPreclusive() const { return( type == PRECLUSIVE ); } - inline bool IsInactive() const { return( type == INACTIVE ); } - inline bool IsPrivacy() const { return( type == PRIVACY ); } - inline const Image *AlarmImage() const { return( image ); } - inline const Polygon &GetPolygon() const { return( polygon ); } - inline bool Alarmed() const { return( alarmed ); } - inline bool WasAlarmed() const { return( was_alarmed ); } - inline void SetAlarm() { was_alarmed = alarmed; alarmed = true; } - inline void ClearAlarm() { was_alarmed = alarmed; alarmed = false; } - inline Coord GetAlarmCentre() const { return( alarm_centre ); } - inline unsigned int Score() const { return( score ); } + public: + Zone( + Monitor *p_monitor, + int p_id, + const char *p_label, + ZoneType p_type, + const Polygon &p_polygon, + const Rgb p_alarm_rgb, + CheckMethod p_check_method, + int p_min_pixel_threshold=15, + int p_max_pixel_threshold=0, + int p_min_alarm_pixels=50, + int p_max_alarm_pixels=75000, + const 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 void ResetStats() - { - alarmed = false; - was_alarmed = false; - pixel_diff = 0; - alarm_pixels = 0; - alarm_filter_pixels = 0; - alarm_blob_pixels = 0; - alarm_blobs = 0; - min_blob_size = 0; - max_blob_size = 0; - score = 0; - } - void RecordStats( const Event *event ); - bool CheckAlarms( const Image *delta_image ); - bool DumpSettings( char *output, bool verbose ); + Zone(Monitor *p_monitor, int p_id, const char *p_label, const Polygon &p_polygon) + : + monitor(p_monitor), + id(p_id), + label(p_label), + blob_stats{}, + stats(p_id) + { + Setup(Zone::INACTIVE, p_polygon, kRGBBlack, (Zone::CheckMethod)0, 0, 0, 0, 0, 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 ); + } - static bool ParsePolygonString( const char *polygon_string, Polygon &polygon ); - static bool ParseZoneString( const char *zone_string, int &zone_id, int &colour, Polygon &polygon ); - static int Load( Monitor *monitor, Zone **&zones ); - //================================================= - bool CheckOverloadCount(); - int GetOverloadCount(); - void SetOverloadCount(int nOverCount); - int GetOverloadFrames(); - //================================================= - bool CheckExtendAlarmCount(); - int GetExtendAlarmCount(); - void SetExtendAlarmCount(int nOverCount); - int GetExtendAlarmFrames(); - void SetScore(unsigned int nScore); - void SetAlarmImage(const Image* srcImage); + Zone(const Zone &z); + ~Zone(); - inline const Image *getPgImage() const { return( pg_image ); } - inline const Range *getRanges() const { return( ranges ); } + inline int Id() const { return id; } + inline const char *Label() const { return label.c_str(); } + inline ZoneType Type() const { return type; } + inline bool IsActive() const { return( type == ACTIVE ); } + inline bool IsInclusive() const { return( type == INCLUSIVE ); } + inline bool IsExclusive() const { return( type == EXCLUSIVE ); } + inline bool IsPreclusive() const { return( type == PRECLUSIVE ); } + inline bool IsInactive() const { return( type == INACTIVE ); } + inline bool IsPrivacy() const { return( type == PRIVACY ); } + inline const Image *AlarmImage() const { return image; } + inline const Polygon &GetPolygon() const { return polygon; } + inline bool Alarmed() const { return alarmed; } + inline bool WasAlarmed() const { return was_alarmed; } + inline void SetAlarm() { was_alarmed = alarmed; alarmed = true; } + inline void ClearAlarm() { was_alarmed = alarmed; alarmed = false; } + inline Vector2 GetAlarmCentre() const { return stats.alarm_centre_; } + inline unsigned int Score() const { return stats.score_; } + + inline void ResetStats() { + alarmed = false; + was_alarmed = false; + stats.Reset(); + } + void RecordStats( const Event *event ); + ZoneStats const &GetStats() const { + stats.DumpToLog("GetStats"); + return stats; + }; + + bool CheckAlarms(const Image *delta_image); + bool DumpSettings(char *output, bool verbose) const; + + static bool ParsePolygonString( const char *polygon_string, Polygon &polygon ); + static bool ParseZoneString( const char *zone_string, int &zone_id, int &colour, Polygon &polygon ); + static std::vector Load(Monitor *monitor); + //================================================= + bool CheckOverloadCount(); + int GetOverloadCount(); + void SetOverloadCount(int nOverCount); + int GetOverloadFrames(); + //================================================= + bool CheckExtendAlarmCount(); + int GetExtendAlarmCount(); + void SetExtendAlarmCount(int nOverCount); + int GetExtendAlarmFrames(); + void SetScore(unsigned int nScore); + void SetAlarmImage(const Image* srcImage); + + inline const Image *getPgImage() const { return pg_image; } + inline const Range *getRanges() const { return ranges; } }; #endif // ZM_ZONE_H diff --git a/src/zm_zone_stats.h b/src/zm_zone_stats.h new file mode 100644 index 000000000..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/zma.cpp b/src/zma.cpp deleted file mode 100644 index a0cb048bf..000000000 --- a/src/zma.cpp +++ /dev/null @@ -1,187 +0,0 @@ -// -// ZoneMinder Analysis Daemon, $Date$, $Revision$ -// Copyright (C) 2001-2008 Philip Coombes -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// - -/* - -=head1 NAME - -zma - The ZoneMinder Analysis daemon - -=head1 SYNOPSIS - - zma -m - zma --monitor - zma -h - zma --help - zma -v - zma --version - -=head1 DESCRIPTION - -This is the component that goes through the captured frames and checks them -for motion which might generate an alarm or event. It generally keeps up with -the Capture daemon but if very busy may skip some frames to prevent it falling -behind. - -=head1 OPTIONS - - -m, --monitor_id - ID of the monitor to analyse - -h, --help - Display usage information - -v, --version - Print the installed version of ZoneMinder - -=cut - -*/ - -#include -#include - -#include "zm.h" -#include "zm_db.h" -#include "zm_signal.h" -#include "zm_monitor.h" -#include "zm_fifo.h" - -void Usage() { - fprintf(stderr, "zma -m \n"); - fprintf(stderr, "Options:\n"); - fprintf(stderr, " -m, --monitor : Specify which monitor to use\n"); - fprintf(stderr, " -h, --help : This screen\n"); - fprintf(stderr, " -v, --version : Report the installed version of ZoneMinder\n"); - exit(0); -} - -int main( int argc, char *argv[] ) { - self = argv[0]; - - srand(getpid() * time(0)); - - int id = -1; - - static struct option long_options[] = { - {"monitor", 1, 0, 'm'}, - {"help", 0, 0, 'h'}, - {"version", 0, 0, 'v'}, - {0, 0, 0, 0} - }; - - while (1) { - int option_index = 0; - - int c = getopt_long(argc, argv, "m:h:v", long_options, &option_index); - if ( c == -1 ) { - break; - } - - switch (c) { - case 'm': - id = atoi(optarg); - break; - case 'h': - case '?': - Usage(); - break; - case 'v': - std::cout << ZM_VERSION << "\n"; - exit(0); - default: - //fprintf( stderr, "?? getopt returned character code 0%o ??\n", c ); - break; - } - } - - if (optind < argc) { - fprintf(stderr, "Extraneous options, "); - while (optind < argc) - printf("%s ", argv[optind++]); - printf("\n"); - Usage(); - } - - if ( id < 0 ) { - fprintf(stderr, "Bogus monitor %d\n", id); - Usage(); - exit(0); - } - - char log_id_string[16]; - snprintf(log_id_string, sizeof(log_id_string), "zma_m%d", id); - - zmLoadConfig(); - - logInit(log_id_string); - - hwcaps_detect(); - - Monitor *monitor = Monitor::Load(id, true, Monitor::ANALYSIS); - zmFifoDbgInit( monitor ); - - if ( monitor ) { - Info("In mode %d/%d, warming up", monitor->GetFunction(), monitor->Enabled()); - - zmSetDefaultHupHandler(); - zmSetDefaultTermHandler(); - zmSetDefaultDieHandler(); - - sigset_t block_set; - sigemptyset(&block_set); - - useconds_t analysis_rate = monitor->GetAnalysisRate(); - unsigned int analysis_update_delay = monitor->GetAnalysisUpdateDelay(); - time_t last_analysis_update_time, cur_time; - monitor->UpdateAdaptiveSkip(); - last_analysis_update_time = time(0); - - while( (!zm_terminate) && monitor->ShmValid() ) { - // Process the next image - sigprocmask(SIG_BLOCK, &block_set, 0); - - // Some periodic updates are required for variable capturing framerate - if ( analysis_update_delay ) { - cur_time = time(0); - if ( (unsigned int)( cur_time - last_analysis_update_time ) > analysis_update_delay ) { - analysis_rate = monitor->GetAnalysisRate(); - monitor->UpdateAdaptiveSkip(); - last_analysis_update_time = cur_time; - } - } - - if ( !monitor->Analyse() ) { - usleep(monitor->Active()?ZM_SAMPLE_RATE:ZM_SUSPENDED_RATE); - } else if ( analysis_rate ) { - usleep(analysis_rate); - } - - if ( zm_reload ) { - monitor->Reload(); - logTerm(); - logInit(log_id_string); - zm_reload = false; - } - sigprocmask(SIG_UNBLOCK, &block_set, 0); - } // end while ! zm_terminate - delete monitor; - } else { - fprintf(stderr, "Can't find monitor with id of %d\n", id); - } - Image::Deinitialise(); - logTerm(); - zmDbClose(); - return 0; -} diff --git a/src/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 ad9dea406..29e9886fd 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -53,35 +53,29 @@ possible, this should run at more or less constant speed. */ -#include -#include -#if defined(__FreeBSD__) -#include -#else -#include -#endif - -#if !defined(MAXINT) -#define MAXINT INT_MAX -#endif - #include "zm.h" +#include "zm_camera.h" #include "zm_db.h" -#include "zm_time.h" -#include "zm_signal.h" +#include "zm_define.h" +#include "zm_fifo.h" #include "zm_monitor.h" +#include "zm_rtsp_server_thread.h" +#include "zm_signal.h" +#include "zm_time.h" +#include "zm_utils.h" +#include 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); @@ -90,7 +84,7 @@ void Usage() { int main(int argc, char *argv[]) { self = argv[0]; - srand(getpid() * time(0)); + srand(getpid() * time(nullptr)); const char *device = ""; const char *protocol = ""; @@ -101,16 +95,16 @@ int main(int argc, char *argv[]) { int monitor_id = -1; static struct option long_options[] = { - {"device", 1, 0, 'd'}, - {"protocol", 1, 0, 'r'}, - {"host", 1, 0, 'H'}, - {"port", 1, 0, 'P'}, - {"path", 1, 0, 'p'}, - {"file", 1, 0, 'f'}, - {"monitor", 1, 0, 'm'}, - {"help", 0, 0, 'h'}, - {"version", 0, 0, 'v'}, - {0, 0, 0, 0} + {"device", 1, nullptr, 'd'}, + {"protocol", 1, nullptr, 'r'}, + {"host", 1, nullptr, 'H'}, + {"port", 1, nullptr, 'P'}, + {"path", 1, nullptr, 'p'}, + {"file", 1, nullptr, 'f'}, + {"monitor", 1, nullptr, 'm'}, + {"help", 0, nullptr, 'h'}, + {"version", 0, nullptr, 'v'}, + {nullptr, 0, nullptr, 0} }; while (1) { @@ -188,36 +182,37 @@ int main(int argc, char *argv[]) { } logInit(log_id_string); - zmLoadConfig(); + zmLoadStaticConfig(); + zmDbConnect(); + zmLoadDBConfig(); logInit(log_id_string); - hwcaps_detect(); + HwCapsDetect(); - Monitor **monitors = 0; - int n_monitors = 0; -#if ZM_HAS_V4L + std::vector> monitors; +#if ZM_HAS_V4L2 if ( device[0] ) { - n_monitors = Monitor::LoadLocalMonitors(device, monitors, Monitor::CAPTURE); + monitors = Monitor::LoadLocalMonitors(device, Monitor::CAPTURE); } else -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 if ( host[0] ) { if ( !port ) port = "80"; - n_monitors = Monitor::LoadRemoteMonitors(protocol, host, port, path, monitors, Monitor::CAPTURE); + monitors = Monitor::LoadRemoteMonitors(protocol, host, port, path, Monitor::CAPTURE); } else if ( file[0] ) { - n_monitors = Monitor::LoadFileMonitors(file, monitors, Monitor::CAPTURE); + monitors = Monitor::LoadFileMonitors(file, Monitor::CAPTURE); } else { - Monitor *monitor = Monitor::Load(monitor_id, true, Monitor::CAPTURE); + std::shared_ptr monitor = Monitor::Load(monitor_id, true, Monitor::CAPTURE); if ( monitor ) { - monitors = new Monitor *[1]; - monitors[0] = monitor; - n_monitors = 1; + monitors.push_back(monitor); } } - if ( !n_monitors ) { + if (monitors.empty()) { Error("No monitors found"); exit(-1); + } else { + Debug(2, "%zu monitors loaded", monitors.size()); } Info("Starting Capture version %s", ZM_VERSION); @@ -225,156 +220,158 @@ int main(int argc, char *argv[]) { zmSetDefaultTermHandler(); zmSetDefaultDieHandler(); - sigset_t block_set; - sigemptyset(&block_set); - - sigaddset(&block_set, SIGHUP); - sigaddset(&block_set, SIGUSR1); - sigaddset(&block_set, SIGUSR2); + struct sigaction sa; + sa.sa_handler = SIG_IGN; //handle signal by ignoring + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (sigaction(SIGCHLD, &sa, 0) == -1) { + Error("Unable to set SIGCHLD to ignore. There may be zombies."); + } int result = 0; - int prime_capture_log_count = 0; - while ( !zm_terminate ) { + while (!zm_terminate) { result = 0; - static char sql[ZM_SQL_SML_BUFSIZ]; - for ( int i = 0; i < n_monitors; i++ ) { - time_t now = (time_t)time(NULL); - monitors[i]->setStartupTime(now); - snprintf(sql, sizeof(sql), - "REPLACE INTO Monitor_Status (MonitorId, Status) VALUES ('%d','Running')", - monitors[i]->Id()); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); + for (const std::shared_ptr &monitor : monitors) { + monitor->LoadCamera(); + + if (!monitor->connect()) { + Warning("Couldn't connect to monitor %d", monitor->Id()); } - } // end foreach monitor + SystemTimePoint now = std::chrono::system_clock::now(); + monitor->SetStartupTime(now); + monitor->SetHeartbeatTime(now); - // Outer primary loop, handles connection to camera - if ( monitors[0]->PrimeCapture() < 0 ) { - if ( prime_capture_log_count % 60 ) { - Error("Failed to prime capture of initial monitor"); - } else { - Debug(1, "Failed to prime capture of initial monitor"); - } - prime_capture_log_count ++; - sleep(10); - continue; - } + std::string sql = stringtf( + "INSERT INTO Monitor_Status (MonitorId,Status,CaptureFPS,AnalysisFPS)" + " VALUES (%u, 'Running',0,0) ON DUPLICATE KEY UPDATE Status='Running',CaptureFPS=0,AnalysisFPS=0", + monitor->Id()); + zmDbDo(sql); - int *capture_delays = new int[n_monitors]; - int *alarm_capture_delays = new int[n_monitors]; - int *next_delays = new int[n_monitors]; - struct timeval * last_capture_times = new struct timeval[n_monitors]; - for ( int i = 0; i < n_monitors; i++ ) { - last_capture_times[i].tv_sec = last_capture_times[i].tv_usec = 0; - capture_delays[i] = monitors[i]->GetCaptureDelay(); - alarm_capture_delays[i] = monitors[i]->GetAlarmCaptureDelay(); - snprintf(sql, sizeof(sql), - "REPLACE INTO Monitor_Status (MonitorId, Status) VALUES ('%d','Connected')", - monitors[i]->Id()); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - } - } // end foreach monitor - - struct timeval now; - struct DeltaTimeval delta_time; - while ( !zm_terminate ) { - //sigprocmask(SIG_BLOCK, &block_set, 0); - for ( int i = 0; i < n_monitors; i++ ) { - long min_delay = MAXINT; - - gettimeofday(&now, NULL); - for ( int j = 0; j < n_monitors; j++ ) { - if ( last_capture_times[j].tv_sec ) { - DELTA_TIMEVAL(delta_time, now, last_capture_times[j], DT_PREC_3); - if ( monitors[i]->GetState() == Monitor::ALARM ) - next_delays[j] = alarm_capture_delays[j]-delta_time.delta; - else - next_delays[j] = capture_delays[j]-delta_time.delta; - if ( next_delays[j] < 0 ) - next_delays[j] = 0; - } else { - next_delays[j] = 0; - } - if ( next_delays[j] <= min_delay ) { - min_delay = next_delays[j]; - } - } // end foreach monitor - - if ( next_delays[i] <= min_delay || next_delays[i] <= 0 ) { - if ( monitors[i]->PreCapture() < 0 ) { - Error("Failed to pre-capture monitor %d %s (%d/%d)", - monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors); - monitors[i]->Close(); - result = -1; - break; - } - if ( monitors[i]->Capture() < 0 ) { - Info("Failed to capture image from monitor %d %s (%d/%d)", - monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors); - monitors[i]->Close(); - result = -1; - break; - } - if ( monitors[i]->PostCapture() < 0 ) { - Error("Failed to post-capture monitor %d %s (%d/%d)", - monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors); - monitors[i]->Close(); - result = -1; - break; - } - - if ( next_delays[i] > 0 ) { - gettimeofday(&now, NULL); - DELTA_TIMEVAL(delta_time, now, last_capture_times[i], DT_PREC_3); - long sleep_time = next_delays[i]-delta_time.delta; - if ( sleep_time > 0 ) { - usleep(sleep_time*(DT_MAXGRAN/DT_PREC_3)); - } - } - gettimeofday(&(last_capture_times[i]), NULL); - } // end if next_delay <= min_delay || next_delays[i] <= 0 ) - - } // end foreach n_monitors - //sigprocmask(SIG_UNBLOCK, &block_set, 0); - if ( zm_reload ) { - for ( int i = 0; i < n_monitors; i++ ) { - monitors[i]->Reload(); + Seconds sleep_time = Seconds(0); + while (monitor->PrimeCapture() <= 0) { + if (prime_capture_log_count % 60) { + logPrintf(Logger::ERROR + monitor->Importance(), + "Failed to prime capture of initial monitor"); + } else { + Debug(1, "Failed to prime capture of initial monitor"); } - logTerm(); - logInit(log_id_string); - zm_reload = false; + + prime_capture_log_count++; + if (zm_terminate) { + break; + } + if (sleep_time < Seconds(60)) { + sleep_time++; + } + + std::this_thread::sleep_for(sleep_time); } - if ( result < 0 ) { - // Failure, try reconnecting - sleep(1); + if (zm_terminate) { break; } - } // end while ! zm_terminate - delete [] alarm_capture_delays; - delete [] capture_delays; - delete [] next_delays; - delete [] last_capture_times; - } // end while ! zm_terminate outer connection loop - for ( int i = 0; i < n_monitors; i++ ) { - static char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), - "REPLACE INTO Monitor_Status (MonitorId, Status) VALUES ('%d','NotRunning')", - monitors[i]->Id()); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); + 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; } - delete monitors[i]; + + std::vector last_capture_times = std::vector(monitors.size()); + Microseconds sleep_time = Microseconds(0); + + while (!zm_terminate) { + for (size_t i = 0; i < monitors.size(); i++) { + monitors[i]->CheckAction(); + + if (monitors[i]->PreCapture() < 0) { + Error("Failed to pre-capture monitor %d %s (%zu/%zu)", + monitors[i]->Id(), monitors[i]->Name(), i + 1, monitors.size()); + result = -1; + break; + } + if (monitors[i]->Capture() < 0) { + Error("Failed to capture image from monitor %d %s (%zu/%zu)", + monitors[i]->Id(), monitors[i]->Name(), i + 1, monitors.size()); + result = -1; + break; + } + if (monitors[i]->PostCapture() < 0) { + Error("Failed to post-capture monitor %d %s (%zu/%zu)", + monitors[i]->Id(), monitors[i]->Name(), i + 1, monitors.size()); + result = -1; + break; + } + monitors[i]->UpdateFPS(); + + // capture_delay is the amount of time we should sleep in useconds to achieve the desired framerate. + 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 - 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) { + // Failure, try reconnecting + break; + } + } // end while ! zm_terminate and connected + + for (std::shared_ptr & monitor : monitors) { + monitor->Close(); + monitor->disconnect(); + } + + if (zm_reload) { + for (std::shared_ptr &monitor : monitors) { + monitor->Reload(); + } + logTerm(); + logInit(log_id_string); + + zm_reload = false; + } // end if zm_reload + } // end while ! zm_terminate outer connection loop + + for (std::shared_ptr &monitor : monitors) { + std::string sql = stringtf( + "INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%u, 'NotRunning') ON DUPLICATE KEY UPDATE Status='NotRunning'", + monitor->Id()); + zmDbDo(sql); } - delete [] monitors; + monitors.clear(); 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 544aa9936..e22fd1ae1 100644 --- a/src/zms.cpp +++ b/src/zms.cpp @@ -17,19 +17,15 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include -#include -#include -#include - #include "zm.h" #include "zm_db.h" #include "zm_user.h" #include "zm_signal.h" -#include "zm_monitor.h" #include "zm_monitorstream.h" #include "zm_eventstream.h" -#include "zm_fifo.h" +#include "zm_fifo_stream.h" +#include +#include bool ValidateAccess(User *user, int mon_id) { bool allowed = true; @@ -50,10 +46,10 @@ bool ValidateAccess(User *user, int mon_id) { return allowed; } -int main(int argc, const char *argv[]) { +int main(int argc, const char *argv[], char **envp) { self = argv[0]; - srand(getpid() * time(0)); + srand(getpid() * time(nullptr)); enum { ZMS_UNKNOWN, ZMS_MONITOR, ZMS_EVENT, ZMS_FIFO } source = ZMS_UNKNOWN; enum { ZMS_JPEG, ZMS_MPEG, ZMS_RAW, ZMS_ZIP, ZMS_SINGLE } mode = ZMS_JPEG; @@ -67,6 +63,7 @@ int main(int argc, const char *argv[]) { double maxfps = 10.0; unsigned int bitrate = 100000; unsigned int ttl = 0; + bool analysis_frames = false; EventStream::StreamMode replay = EventStream::MODE_NONE; std::string username; std::string password; @@ -86,96 +83,114 @@ int main(int argc, const char *argv[]) { nph = true; } - zmLoadConfig(); char log_id_string[32] = "zms"; logInit(log_id_string); + zmLoadStaticConfig(); + zmDbConnect(); + zmLoadDBConfig(); + logInit(log_id_string); + + for (char **env = envp; *env != 0; env++) { + char *thisEnv = *env; + Debug(1, "env: %s", thisEnv); + } const char *query = getenv("QUERY_STRING"); - if ( query ) { - Debug(1, "Query: %s", query); - - char temp_query[1024]; - strncpy(temp_query, query, sizeof(temp_query)); - char *q_ptr = temp_query; - char *parms[16]; // Shouldn't be more than this - int parm_no = 0; - while ( (parm_no < 16) && (parms[parm_no] = strtok(q_ptr, "&")) ) { - parm_no++; - q_ptr = NULL; - } - - for ( int p = 0; p < parm_no; p++ ) { - char *name = strtok(parms[p], "="); - char *value = strtok(NULL, "="); - if ( !value ) - value = (char *)""; - if ( !strcmp(name, "source") ) { - source = !strcmp(value, "event")?ZMS_EVENT:ZMS_MONITOR; - if ( !strcmp(value, "fifo") ) - source = ZMS_FIFO; - } else if ( !strcmp(name, "mode") ) { - mode = !strcmp(value, "jpeg")?ZMS_JPEG:ZMS_MPEG; - mode = !strcmp(value, "raw")?ZMS_RAW:mode; - mode = !strcmp(value, "zip")?ZMS_ZIP:mode; - mode = !strcmp(value, "single")?ZMS_SINGLE:mode; - } else if ( !strcmp(name, "format") ) { - strncpy( format, value, sizeof(format) ); - } else if ( !strcmp(name, "monitor") ) { - monitor_id = atoi(value); - if ( source == ZMS_UNKNOWN ) - source = ZMS_MONITOR; - } else if ( !strcmp(name, "time") ) { - event_time = atoi(value); - } else if ( !strcmp(name, "event") ) { - event_id = strtoull(value, (char **)NULL, 10); - source = ZMS_EVENT; - } else if ( !strcmp(name, "frame") ) { - frame_id = strtoull(value, (char **)NULL, 10); - source = ZMS_EVENT; - } else if ( !strcmp(name, "scale") ) { - scale = atoi(value); - } else if ( !strcmp(name, "rate") ) { - rate = atoi(value); - } else if ( !strcmp(name, "maxfps") ) { - maxfps = atof(value); - } else if ( !strcmp(name, "bitrate") ) { - bitrate = atoi(value); - } else if ( !strcmp(name, "ttl") ) { - ttl = atoi(value); - } else if ( !strcmp(name, "replay") ) { - if ( !strcmp(value, "gapless") ) { - replay = EventStream::MODE_ALL_GAPLESS; - } else if ( !strcmp(value, "all") ) { - replay = EventStream::MODE_ALL; - } else if ( !strcmp(value, "none") ) { - replay = EventStream::MODE_NONE; - } else if ( !strcmp(value, "single") ) { - replay = EventStream::MODE_SINGLE; - } else { - Error("Unsupported value %s for replay, defaulting to none", value); - } - } else if ( !strcmp(name, "connkey") ) { - connkey = atoi(value); - } else if ( !strcmp(name, "buffer") ) { - playback_buffer = atoi(value); - } else if ( !strcmp(name, "auth") ) { - strncpy( auth, value, sizeof(auth)-1 ); - } else if ( !strcmp(name, "token") ) { - jwt_token_str = value; - Debug(1, "ZMS: JWT token found: %s", jwt_token_str.c_str()); - } else if ( !strcmp(name, "user") ) { - username = UriDecode(value); - } else if ( !strcmp(name, "pass") ) { - password = UriDecode(value); - Debug(1, "Have %s for password", password.c_str()); - } else { - Debug(1, "Unknown parameter passed to zms %s=%s", name, value); - } // end if possible parameter names - } // end foreach parm - } else { + if ( query == nullptr ) { Fatal("No query string."); + return 0; } // end if query + Debug(1, "Query: %s", query); + + char *q_ptr = (char *)query; + char *parms[16]; // Shouldn't be more than this + int parm_no = 0; + while ( (parm_no < 16) && (parms[parm_no] = strtok(q_ptr, "&")) ) { + parm_no++; + q_ptr = nullptr; + } + + for ( int p = 0; p < parm_no; p++ ) { + char *name = strtok(parms[p], "="); + char const *value = strtok(nullptr, "="); + if ( !value ) + value = ""; + if ( !strcmp(name, "analysis") ) { + if ( !strcmp(value, "true") ) { + analysis_frames = true; + } else { + analysis_frames = (atoi(value) == 1); + } + Debug(1, "Viewing analysis frames"); + } else if ( !strcmp(name, "source") ) { + if ( !strcmp(value, "event") ) { + source = ZMS_EVENT; + } else if ( !strcmp(value, "fifo") ) { + source = ZMS_FIFO; + } else { + source = ZMS_MONITOR; + } + } else if ( !strcmp(name, "mode") ) { + mode = !strcmp(value, "jpeg")?ZMS_JPEG:ZMS_MPEG; + mode = !strcmp(value, "raw")?ZMS_RAW:mode; + mode = !strcmp(value, "zip")?ZMS_ZIP:mode; + mode = !strcmp(value, "single")?ZMS_SINGLE:mode; + } else if ( !strcmp(name, "format") ) { + strncpy(format, value, sizeof(format)-1); + } else if ( !strcmp(name, "monitor") ) { + monitor_id = atoi(value); + if ( source == ZMS_UNKNOWN ) + source = ZMS_MONITOR; + } else if ( !strcmp(name, "time") ) { + event_time = atoi(value); + } else if ( !strcmp(name, "event") ) { + event_id = strtoull(value, nullptr, 10); + source = ZMS_EVENT; + } else if ( !strcmp(name, "frame") ) { + frame_id = strtoull(value, nullptr, 10); + source = ZMS_EVENT; + } else if ( !strcmp(name, "scale") ) { + scale = atoi(value); + } else if ( !strcmp(name, "rate") ) { + rate = atoi(value); + } else if ( !strcmp(name, "maxfps") ) { + maxfps = atof(value); + } else if ( !strcmp(name, "bitrate") ) { + bitrate = atoi(value); + } else if ( !strcmp(name, "ttl") ) { + ttl = atoi(value); + } else if ( !strcmp(name, "replay") ) { + if ( !strcmp(value, "gapless") ) { + replay = EventStream::MODE_ALL_GAPLESS; + } else if ( !strcmp(value, "all") ) { + replay = EventStream::MODE_ALL; + } else if ( !strcmp(value, "none") ) { + replay = EventStream::MODE_NONE; + } else if ( !strcmp(value, "single") ) { + replay = EventStream::MODE_SINGLE; + } else { + Error("Unsupported value %s for replay, defaulting to none", value); + } + } else if ( !strcmp(name, "connkey") ) { + connkey = atoi(value); + } else if ( !strcmp(name, "buffer") ) { + playback_buffer = atoi(value); + } else if ( !strcmp(name, "auth") ) { + strncpy(auth, value, sizeof(auth)-1); + } else if ( !strcmp(name, "token") ) { + jwt_token_str = value; + Debug(1, "ZMS: JWT token found: %s", jwt_token_str.c_str()); + } else if ( !strcmp(name, "user") ) { + username = UriDecode(value); + } else if ( !strcmp(name, "pass") ) { + password = UriDecode(value); + Debug(1, "Have %s for password", password.c_str()); + } else { + Debug(1, "Unknown parameter passed to zms %s=%s", name, value); + } // end if possible parameter names + } // end foreach parm + if ( monitor_id ) { snprintf(log_id_string, sizeof(log_id_string), "zms_m%d", monitor_id); } else { @@ -184,7 +199,7 @@ int main(int argc, const char *argv[]) { logInit(log_id_string); if ( config.opt_use_auth ) { - User *user = 0; + User *user = nullptr; if ( jwt_token_str != "" ) { // user = zmLoadTokenUser(jwt_token_str, config.auth_hash_ips); @@ -195,53 +210,55 @@ int main(int argc, const char *argv[]) { } else { Error("Bad username"); } - } else { - // if ( strcmp( config.auth_relay, "hashed" ) == 0 ) - { - if ( *auth ) { - user = zmLoadAuthUser(auth, config.auth_hash_ips); - } - } - // else if ( strcmp( config.auth_relay, "plain" ) == 0 ) - { - if ( username.length() && password.length() ) { - user = zmLoadUser(username.c_str(), password.c_str()); - } + if ( *auth ) { + user = zmLoadAuthUser(auth, config.auth_hash_ips); + } else if ( username.length() && password.length() ) { + user = zmLoadUser(username.c_str(), password.c_str()); } } if ( !user ) { fputs("HTTP/1.0 403 Forbidden\r\n\r\n", stdout); - Error("Unable to authenticate user"); + + const char *referer = getenv("HTTP_REFERER"); + Error("Unable to authenticate user from %s", referer); logTerm(); zmDbClose(); return 0; } if ( !ValidateAccess(user, monitor_id) ) { + delete user; + user = nullptr; fputs("HTTP/1.0 403 Forbidden\r\n\r\n", stdout); logTerm(); zmDbClose(); return 0; } + delete user; + user = nullptr; } // end if config.opt_use_auth - hwcaps_detect(); + HwCapsDetect(); + Image::Initialise(); zmSetDefaultTermHandler(); zmSetDefaultDieHandler(); - setbuf(stdout, 0); + setbuf(stdout, nullptr); if ( nph ) { fputs("HTTP/1.0 200 OK\r\n", stdout); } fprintf(stdout, "Server: ZoneMinder Video Server/%s\r\n", ZM_VERSION); - time_t now = time(0); + time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); char date_string[64]; - strftime(date_string, sizeof(date_string)-1, "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now)); + tm now_tm = {}; + strftime(date_string, sizeof(date_string)-1, + "%a, %d %b %Y %H:%M:%S GMT", gmtime_r(&now, &now_tm)); - fprintf(stdout, "Last-Modified: %s\r\n", date_string); + fputs("Last-Modified: ", stdout); + fputs(date_string, stdout); fputs( - "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" + "\r\nExpires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" "Cache-Control: no-store, no-cache, must-revalidate\r\n" "Cache-Control: post-check=0, pre-check=0\r\n" "Pragma: no-cache\r\n", @@ -256,13 +273,13 @@ int main(int argc, const char *argv[]) { stream.setStreamQueue(connkey); stream.setStreamBuffer(playback_buffer); if ( !stream.setStreamStart(monitor_id) ) { - Error("Unable to connect to zmc process for monitor %d", monitor_id); - fprintf(stderr, "Unable to connect to zmc process. " - " Please ensure that it is running."); + fputs("Content-Type: multipart/x-mixed-replace; boundary=" BOUNDARY "\r\n\r\n", stdout); + stream.sendTextFrame("Unable to connect to monitor"); logTerm(); zmDbClose(); return -1; } + stream.setStreamFrameType(analysis_frames ? StreamBase::FRAME_ANALYSIS: StreamBase::FRAME_NORMAL); if ( mode == ZMS_JPEG ) { stream.setStreamType(MonitorStream::STREAM_JPEG); @@ -273,17 +290,9 @@ int main(int argc, const char *argv[]) { } 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.\nYou 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 ) { @@ -307,30 +316,24 @@ int main(int argc, const char *argv[]) { Debug(3, "Setting stream start to frame (%d)", frame_id); stream.setStreamStart(event_id, frame_id); } + stream.setStreamFrameType(analysis_frames ? StreamBase::FRAME_ANALYSIS: StreamBase::FRAME_NORMAL); 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 { Error("Neither a monitor or event was specified."); } // end if monitor or event + Debug(1, "Terminating"); + Image::Deinitialise(); logTerm(); + dbQueue.stop(); zmDbClose(); - return(0); + return 0; } diff --git a/src/zmu.cpp b/src/zmu.cpp index bd70f8337..b85227fc4 100644 --- a/src/zmu.cpp +++ b/src/zmu.cpp @@ -86,15 +86,14 @@ Options for use with monitors: */ -#include -#include - #include "zm.h" #include "zm_db.h" #include "zm_user.h" #include "zm_signal.h" #include "zm_monitor.h" #include "zm_local_camera.h" +#include +#include void Usage(int status=-1) { fputs( @@ -184,7 +183,7 @@ bool ValidateAccess(User *user, int mon_id, int function) { if ( user->getMonitors() < User::PERM_VIEW ) allowed = false; } - if ( function & (ZMU_ALARM|ZMU_NOALARM|ZMU_CANCEL|ZMU_RELOAD|ZMU_ENABLE|ZMU_DISABLE|ZMU_SUSPEND|ZMU_RESUME|ZMU_BRIGHTNESS|ZMU_CONTRAST|ZMU_HUE|ZMU_COLOUR) ) { + if ( function & (ZMU_NOALARM|ZMU_RELOAD|ZMU_ENABLE|ZMU_DISABLE|ZMU_SUSPEND|ZMU_RESUME|ZMU_BRIGHTNESS|ZMU_CONTRAST|ZMU_HUE|ZMU_COLOUR) ) { if ( user->getMonitors() < User::PERM_EDIT ) allowed = false; } @@ -196,12 +195,12 @@ bool ValidateAccess(User *user, int mon_id, int function) { return allowed; } -int exit_zmu(int exit_code) { +void exit_zmu(int exit_code) { logTerm(); + dbQueue.stop(); zmDbClose(); exit(exit_code); - return exit_code; } int main(int argc, char *argv[]) { @@ -212,45 +211,45 @@ int main(int argc, char *argv[]) { self = argv[0]; - srand(getpid() * time(0)); + srand(getpid() * time(nullptr)); static struct option long_options[] = { - {"device", 2, 0, 'd'}, - {"monitor", 1, 0, 'm'}, - {"verbose", 0, 0, 'v'}, - {"image", 2, 0, 'i'}, - {"scale", 1, 0, 'S'}, - {"timestamp", 2, 0, 't'}, - {"state", 0, 0, 's'}, - {"brightness", 2, 0, 'B'}, - {"contrast", 2, 0, 'C'}, - {"hue", 2, 0, 'H'}, - {"contrast", 2, 0, 'O'}, - {"read_index", 0, 0, 'R'}, - {"write_index", 0, 0, 'W'}, - {"event", 0, 0, 'e'}, - {"fps", 0, 0, 'f'}, - {"zones", 2, 0, 'z'}, - {"alarm", 0, 0, 'a'}, - {"noalarm", 0, 0, 'n'}, - {"cancel", 0, 0, 'c'}, - {"reload", 0, 0, 'L'}, - {"enable", 0, 0, 'E'}, - {"disable", 0, 0, 'D'}, - {"suspend", 0, 0, 'u'}, - {"resume", 0, 0, 'r'}, - {"query", 0, 0, 'q'}, - {"username", 1, 0, 'U'}, - {"password", 1, 0, 'P'}, - {"auth", 1, 0, 'A'}, - {"token", 1, 0, 'T'}, - {"version", 1, 0, 'V'}, - {"help", 0, 0, 'h'}, - {"list", 0, 0, 'l'}, - {0, 0, 0, 0} + {"device", 2, nullptr, 'd'}, + {"monitor", 1, nullptr, 'm'}, + {"verbose", 0, nullptr, 'v'}, + {"image", 2, nullptr, 'i'}, + {"scale", 1, nullptr, 'S'}, + {"timestamp", 2, nullptr, 't'}, + {"state", 0, nullptr, 's'}, + {"brightness", 2, nullptr, 'B'}, + {"contrast", 2, nullptr, 'C'}, + {"hue", 2, nullptr, 'H'}, + {"contrast", 2, nullptr, 'O'}, + {"read_index", 0, nullptr, 'R'}, + {"write_index", 0, nullptr, 'W'}, + {"event", 0, nullptr, 'e'}, + {"fps", 0, nullptr, 'f'}, + {"zones", 2, nullptr, 'z'}, + {"alarm", 0, nullptr, 'a'}, + {"noalarm", 0, nullptr, 'n'}, + {"cancel", 0, nullptr, 'c'}, + {"reload", 0, nullptr, 'L'}, + {"enable", 0, nullptr, 'E'}, + {"disable", 0, nullptr, 'D'}, + {"suspend", 0, nullptr, 'u'}, + {"resume", 0, nullptr, 'r'}, + {"query", 0, nullptr, 'q'}, + {"username", 1, nullptr, 'U'}, + {"password", 1, nullptr, 'P'}, + {"auth", 1, nullptr, 'A'}, + {"token", 1, nullptr, 'T'}, + {"version", 1, nullptr, 'V'}, + {"help", 0, nullptr, 'h'}, + {"list", 0, nullptr, 'l'}, + {nullptr, 0, nullptr, 0} }; - const char *device = 0; + std::string device; int mon_id = 0; bool verbose = false; int function = ZMU_BOGUS; @@ -258,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; - char *zoneString = 0; - char *username = 0; - char *password = 0; - char *auth = 0; + 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::U:P:A:V:T:", long_options, &option_index); - if ( c == -1 ) { + int c = getopt_long(argc, argv, "d:m:vsEDLurwei::S:t::fz::ancqhlB::C::H::O::RWU:P:A:V:T:", long_options, &option_index); + if (c == -1) { break; } switch (c) { case 'd': - if ( optarg ) + if (optarg) device = optarg; break; case 'm': @@ -297,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': @@ -305,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': @@ -322,7 +324,7 @@ int main(int argc, char *argv[]) { break; case 'z': function |= ZMU_ZONES; - if ( optarg ) + if (optarg) zoneString = optarg; break; case 'a': @@ -354,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; @@ -384,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); @@ -410,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(); } @@ -420,8 +430,10 @@ int main(int argc, char *argv[]) { } //printf( "Monitor %d, Function %d\n", mon_id, function ); - zmLoadConfig(); - + logInit("zmu"); + zmLoadStaticConfig(); + zmDbConnect(); + zmLoadDBConfig(); logInit("zmu"); zmSetDefaultTermHandler(); @@ -430,7 +442,9 @@ int main(int argc, char *argv[]) { User *user = 0; if ( config.opt_use_auth ) { - if ( strcmp(config.auth_relay, "none") == 0 ) { + if ( jwt_token_str != "" ) { + user = zmLoadTokenUser(jwt_token_str, false); + } else if ( strcmp(config.auth_relay, "none") == 0 ) { if ( !username ) { Error("Username must be supplied"); exit_zmu(-1); @@ -444,13 +458,10 @@ int main(int argc, char *argv[]) { user = zmLoadUser(username); } else { - if ( !(username && password) && !auth && (jwt_token_str=="")) { + if ( !(username && password) && !auth ) { Error("Username and password or auth/token string must be supplied"); exit_zmu(-1); } - if (jwt_token_str != "") { - user = zmLoadTokenUser(jwt_token_str, false); - } if ( auth ) { user = zmLoadAuthUser(auth, false); } @@ -471,292 +482,321 @@ int main(int argc, char *argv[]) { exit_zmu(-1); } if ( !ValidateAccess(user, mon_id, function) ) { - Error("Insufficient privileges for requested action"); + Error("Insufficient privileges for user %s for requested function %x", username, function); exit_zmu(-1); } } // end if auth if ( mon_id > 0 ) { - //fprintf(stderr,"Monitor %d\n", mon_id); - Monitor *monitor = Monitor::Load(mon_id, function&(ZMU_QUERY|ZMU_ZONES), Monitor::QUERY); - if ( monitor ) { - //fprintf(stderr,"Monitor %d(%s)\n", monitor->Id(), monitor->Name()); - if ( verbose ) { - printf("Monitor %d(%s)\n", monitor->Id(), monitor->Name()); - } - if ( ! monitor->connect() ) { - Error("Can't connect to capture daemon: %d %s", monitor->Id(), monitor->Name()); - exit_zmu(-1); - } + std::shared_ptr monitor = Monitor::Load(mon_id, function&(ZMU_QUERY|ZMU_ZONES), Monitor::QUERY); + if ( !monitor ) { + Error("Unable to load monitor %d", mon_id); + exit_zmu(-1); + } // end if ! MONITOR - char separator = ' '; - bool have_output = false; - if ( function & ZMU_STATE ) { - Monitor::State state = monitor->GetState(); - if ( verbose ) { - printf("Current state: %s\n", state==Monitor::ALARM?"Alarm":(state==Monitor::ALERT?"Alert":"Idle")); - } else { - if ( have_output ) printf("%c", separator); - printf("%d", state); - have_output = true; - } + if ( verbose ) { + printf("Monitor %u(%s)\n", monitor->Id(), monitor->Name()); + } + + if (monitor->GetFunction() == Monitor::NONE) { + if (verbose) { + printf("Current state: None\n"); + } else { + printf("%d", Monitor::UNKNOWN); } - if ( function & ZMU_TIME ) { - struct timeval timestamp = monitor->GetTimestamp( image_idx ); - if ( verbose ) { - char timestamp_str[64] = "None"; - if ( timestamp.tv_sec ) - strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", localtime(×tamp.tv_sec)); - if ( 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); - } else { - if ( have_output ) printf("%c", separator); - printf("%ld.%02ld", timestamp.tv_sec, timestamp.tv_usec/10000); - have_output = true; - } - } - if ( function & ZMU_READ_IDX ) { - if ( verbose ) - printf("Last read index: %d\n", monitor->GetLastReadIndex()); - else { - if ( have_output ) printf("%c", separator); - printf("%d", monitor->GetLastReadIndex()); - have_output = true; - } - } - if ( function & ZMU_WRITE_IDX ) { - if ( verbose ) { - printf("Last write index: %d\n", monitor->GetLastWriteIndex()); - } else { - if ( have_output ) printf("%c", separator); - printf("%d", monitor->GetLastWriteIndex()); - have_output = true; - } - } - if ( function & ZMU_EVENT ) { - if ( verbose ) { - printf("Last event id: %" PRIu64 "\n", monitor->GetLastEventId()); - } else { - if ( have_output ) printf("%c", separator); - printf("%" PRIu64, monitor->GetLastEventId()); - have_output = true; - } - } - if ( function & ZMU_FPS ) { - if ( verbose ) - printf("Current capture rate: %.2f frames per second\n", monitor->GetFPS()); - else { - if ( have_output ) printf("%c", separator); - printf("%.2f", monitor->GetFPS()); - have_output = true; - } - } - if ( function & ZMU_IMAGE ) { - if ( verbose ) { - if ( image_idx == -1 ) - printf("Dumping last image captured to Monitor%d.jpg", monitor->Id()); - else - printf("Dumping buffer image %d to Monitor%d.jpg", image_idx, monitor->Id()); - if ( scale != -1 ) - printf(", scaling by %d%%", scale); - printf("\n"); - } - monitor->GetImage(image_idx, scale>0?scale:100); - } - if ( function & ZMU_ZONES ) { - if ( verbose ) - printf("Dumping zone image to Zones%d.jpg\n", monitor->Id()); - monitor->DumpZoneImage(zoneString); - } - if ( function & ZMU_ALARM ) { - if ( monitor->GetFunction() == Monitor::Function::MONITOR ) { - printf("A Monitor in monitor mode cannot handle alarms. Please use NoDect\n"); - } else { - if ( verbose ) - printf("Forcing alarm on\n"); - monitor->ForceAlarmOn(config.forced_alarm_score, "Forced Web"); - while ( (monitor->GetState() != Monitor::ALARM) && !zm_terminate ) { - // Wait for monitor to notice. - usleep(1000); - } - printf("Alarmed event id: %" PRIu64 "\n", monitor->GetLastEventId()); - } // end if ! MONITOR - } - if ( function & ZMU_NOALARM ) { - if ( verbose ) - printf("Forcing alarm off\n"); - monitor->ForceAlarmOff(); - } - if ( function & ZMU_CANCEL ) { - if ( verbose ) - printf("Cancelling forced alarm on/off\n"); - monitor->CancelForced(); - } - if ( function & ZMU_RELOAD ) { - if ( verbose ) - printf("Reloading monitor settings\n"); - monitor->actionReload(); - } - if ( function & ZMU_ENABLE ) { - if ( verbose ) - printf("Enabling event generation\n"); - monitor->actionEnable(); - } - if ( function & ZMU_DISABLE ) { - if ( verbose ) - printf("Disabling event generation\n"); - monitor->actionDisable(); - } - if ( function & ZMU_SUSPEND ) { - if ( verbose ) - printf("Suspending event generation\n"); - monitor->actionSuspend(); - } - if ( function & ZMU_RESUME ) { - if ( verbose ) - printf("Resuming event generation\n"); - monitor->actionResume(); - } - if ( function & ZMU_QUERY ) { - char monString[16382] = ""; - monitor->DumpSettings(monString, verbose); - printf("%s\n", monString); - } - if ( function & ZMU_BRIGHTNESS ) { - if ( verbose ) { - if ( brightness >= 0 ) - printf("New brightness: %d\n", monitor->actionBrightness(brightness)); - else - printf("Current brightness: %d\n", monitor->actionBrightness()); - } else { - if ( have_output ) printf("%c", separator); - if ( brightness >= 0 ) - 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)); - else - printf("Current contrast: %d\n", monitor->actionContrast()); - } else { - if ( have_output ) printf("%c", separator); - if ( contrast >= 0 ) - printf("%d", monitor->actionContrast(contrast)); - else - printf("%d", monitor->actionContrast()); - have_output = true; - } - } - if ( function & ZMU_HUE ) { - if ( verbose ) { - if ( hue >= 0 ) - printf("New hue: %d\n", monitor->actionHue(hue)); - else - printf("Current hue: %d\n", monitor->actionHue()); - } else { - if ( have_output ) printf("%c", separator); - if ( hue >= 0 ) - printf("%d", monitor->actionHue(hue)); - else - printf("%d", monitor->actionHue()); - have_output = true; - } - } - if ( function & ZMU_COLOUR ) { - if ( verbose ) { - if ( colour >= 0 ) - printf("New colour: %d\n", monitor->actionColour(colour)); - else - printf("Current colour: %d\n", monitor->actionColour()); - } else { - if ( have_output ) printf("%c", separator); - if ( colour >= 0 ) - printf("%d", monitor->actionColour(colour)); - else - printf("%d", monitor->actionColour()); - have_output = true; - } - } - if ( have_output ) { - printf("\n"); - } - if ( !function ) { - Usage(); - } - delete monitor; - } else { - Error("Invalid monitor id %d", mon_id); exit_zmu(-1); } - } else { + + if ( !monitor->connect() ) { + Error("Can't connect to capture daemon: %d %s", monitor->Id(), monitor->Name()); + exit_zmu(-1); + } + + char separator = ' '; + bool have_output = false; + if ( function & ZMU_STATE ) { + Monitor::State state = monitor->GetState(); + if ( verbose ) { + printf("Current state: %s\n", state==Monitor::ALARM?"Alarm":(state==Monitor::ALERT?"Alert":"Idle")); + } else { + printf("%d", state); + have_output = true; + } + } + if ( function & ZMU_TIME ) { + SystemTimePoint timestamp = monitor->GetTimestamp(image_idx); + if (verbose) { + char timestamp_str[64] = "None"; + 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("%.2f", FPSeconds(timestamp.time_since_epoch()).count()); + have_output = true; + } + } + if ( function & ZMU_READ_IDX ) { + if ( verbose ) + printf("Last read index: %u\n", monitor->GetLastReadIndex()); + else { + if ( have_output ) fputc(separator, stdout); + printf("%u", monitor->GetLastReadIndex()); + have_output = true; + } + } + if ( function & ZMU_WRITE_IDX ) { + if ( verbose ) { + printf("Last write index: %u\n", monitor->GetLastWriteIndex()); + } else { + if ( have_output ) fputc(separator, stdout); + printf("%u", monitor->GetLastWriteIndex()); + have_output = true; + } + } + if ( function & ZMU_EVENT ) { + if ( verbose ) { + printf("Last event id: %" PRIu64 "\n", monitor->GetLastEventId()); + } else { + if ( have_output ) fputc(separator, stdout); + printf("%" PRIu64, monitor->GetLastEventId()); + have_output = true; + } + } + if ( function & ZMU_FPS ) { + if ( verbose ) { + printf("Current capture rate: %.2f frames per second, analysis rate: %.2f frames per second\n", + monitor->get_capture_fps(), monitor->get_analysis_fps()); + } else { + if ( have_output ) fputc(separator, stdout); + printf("capture: %.2f, analysis: %.2f", monitor->get_capture_fps(), monitor->get_analysis_fps()); + have_output = true; + } + } + if ( function & ZMU_IMAGE ) { + if ( verbose ) { + if ( image_idx == -1 ) + printf("Dumping last image captured to Monitor%u.jpg", monitor->Id()); + else + printf("Dumping buffer image %d to Monitor%u.jpg", image_idx, monitor->Id()); + if ( scale != -1 ) + printf(", scaling by %d%%", scale); + printf("\n"); + } + monitor->GetImage(image_idx, scale>0?scale:100); + } + if ( function & ZMU_ZONES ) { + if ( verbose ) + printf("Dumping zone image to Zones%u.jpg\n", monitor->Id()); + monitor->DumpZoneImage(zoneString); + } + if ( function & ZMU_ALARM ) { + if ( monitor->GetFunction() == Monitor::Function::MONITOR ) { + printf("A Monitor in monitor mode cannot handle alarms. Please use NoDect\n"); + } else { + Monitor::State state = monitor->GetState(); + + if ( verbose ) { + printf("Forcing alarm on current state: %s, event %" PRIu64 "\n", + state==Monitor::ALARM?"Alarm":(state==Monitor::ALERT?"Alert":"Idle"), + 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"); + + Microseconds wait_time = Seconds(10); + while ((monitor->GetState() != Monitor::ALARM) and !zm_terminate and wait_time > Seconds(0)) { + // Wait for monitor to notice. + Microseconds sleep = Microseconds(1); + std::this_thread::sleep_for(sleep); + wait_time -= sleep; + } + + 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()); + } + } + } // end if ZMU_ALARM + + if ( function & ZMU_NOALARM ) { + if ( verbose ) + printf("Forcing alarm off\n"); + monitor->ForceAlarmOff(); + } + if ( function & ZMU_CANCEL ) { + if ( verbose ) + printf("Cancelling forced alarm on/off\n"); + monitor->CancelForced(); + } + if ( function & ZMU_RELOAD ) { + if ( verbose ) + printf("Reloading monitor settings\n"); + monitor->actionReload(); + } + if ( function & ZMU_ENABLE ) { + if ( verbose ) + printf("Enabling event generation\n"); + monitor->actionEnable(); + } + if ( function & ZMU_DISABLE ) { + if ( verbose ) + printf("Disabling event generation\n"); + monitor->actionDisable(); + } + if ( function & ZMU_SUSPEND ) { + if ( verbose ) + printf("Suspending event generation\n"); + monitor->actionSuspend(); + } + if ( function & ZMU_RESUME ) { + if ( verbose ) + printf("Resuming event generation\n"); + monitor->actionResume(); + } if ( function & ZMU_QUERY ) { -#if ZM_HAS_V4L + char monString[16382] = ""; + monitor->DumpSettings(monString, verbose); + printf("%s\n", monString); + } + 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 (have_brightness) + printf("%d", monitor->actionBrightness(brightness)); + else + printf("%d", monitor->actionBrightness()); + have_output = true; + } + } + 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 (have_contrast) + printf("%d", monitor->actionContrast(contrast)); + else + printf("%d", monitor->actionContrast()); + have_output = true; + } + } + 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 (have_hue) + printf("%d", monitor->actionHue(hue)); + else + printf("%d", monitor->actionHue()); + have_output = true; + } + } + 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 (have_colour) + printf("%d", monitor->actionColour(colour)); + else + printf("%d", monitor->actionColour()); + have_output = true; + } + } + + if (have_output) { + printf("\n"); + } + if ( !function ) { + Usage(); + } + } else { // non monitor functions + if ( function & ZMU_QUERY ) { +#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 ) { - sql += "where Function != 'None'"; + std::string sql = "SELECT `Id`, `Function`+0 FROM `Monitors`"; + if (!verbose) { + sql += "WHERE `Function` != 'None'"; } - sql += " order by Id asc"; + 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++ ) { - int mon_id = atoi(dbrow[0]); - int function = atoi(dbrow[1]); - if ( !user || user->canAccess(mon_id) ) { - if ( function > 1 ) { - Monitor *monitor = Monitor::Load(mon_id, false, Monitor::QUERY); + for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { + int monitor_id = atoi(dbrow[0]); + int monitor_function = atoi(dbrow[1]); + if ( !user || user->canAccess(monitor_id) ) { + if ( monitor_function > 1 ) { + std::shared_ptr monitor = Monitor::Load(monitor_id, false, Monitor::QUERY); if ( monitor && monitor->connect() ) { - struct timeval tv = monitor->GetTimestamp(); - printf( "%4d%5d%6d%9d%11ld.%02ld%6d%6d%8" PRIu64 "%8.2f\n", + SystemTimePoint timestamp = monitor->GetTimestamp(); + + printf( "%4d%5d%6d%9d%14.2f%6d%6d%8" PRIu64 "%8.2f\n", monitor->Id(), - function, + 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(), monitor->GetFPS() ); - delete monitor; } } 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, @@ -767,8 +807,9 @@ int main(int argc, char *argv[]) { } // end foreach row mysql_free_result(result); } // end if function && ZMU_LIST - } + } // end if monitor id or not delete user; - return exit_zmu(0); -} + exit_zmu(0); + return 0; +} // end int main() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..880a3d3db --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,43 @@ +# This file is part of the ZoneMinder Project. +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + +include(Catch) + +set(TEST_SOURCES + zm_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}) + +target_link_libraries(tests + PRIVATE + zm-core-interface + zm + ${ZM_BIN_LIBS} + bcrypt + Catch2::Catch2) + +target_include_directories(tests + PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}) + +catch_discover_tests(tests) + +add_custom_command(TARGET tests + PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/data/ ${CMAKE_CURRENT_BINARY_DIR}/data/ + BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/data/) diff --git a/tests/data/fonts/01_bad_magic.zmfnt b/tests/data/fonts/01_bad_magic.zmfnt new file mode 100644 index 000000000..2cfaf0818 Binary files /dev/null and b/tests/data/fonts/01_bad_magic.zmfnt differ diff --git a/tests/data/fonts/02_variant_invalid.zmfnt b/tests/data/fonts/02_variant_invalid.zmfnt new file mode 100644 index 000000000..9b53e85ab Binary files /dev/null and b/tests/data/fonts/02_variant_invalid.zmfnt differ diff --git a/tests/data/fonts/03_missing_cps.zmfnt b/tests/data/fonts/03_missing_cps.zmfnt new file mode 100644 index 000000000..71c58970d Binary files /dev/null and b/tests/data/fonts/03_missing_cps.zmfnt differ diff --git a/tests/data/fonts/04_valid.zmfnt b/tests/data/fonts/04_valid.zmfnt new file mode 100644 index 000000000..0a4ad6653 Binary files /dev/null and b/tests/data/fonts/04_valid.zmfnt differ diff --git a/tests/data/fonts/generate_fonts.py b/tests/data/fonts/generate_fonts.py new file mode 100644 index 000000000..275c2021a --- /dev/null +++ b/tests/data/fonts/generate_fonts.py @@ -0,0 +1,57 @@ +#!/bin/python + +import struct + +GOOD_MAGIC = b"ZMFNT\0" +BAD_MAGIC = b"ABCDE\0" +NUM_FONT_SIZES = 4 + +ZMFNT_VERSION = 1 + + +class FontFile: + def __init__(self, path): + self.path = path + + def write_file_header(self, magic): + with open(self.path, "wb") as f: + f.write(magic) + f.write(struct.pack("B", ZMFNT_VERSION)) + f.write(struct.pack("B", 0)) # pad + + def write_bm_header(self, height, width, cp_count, idx, padding): + with open(self.path, "ab") as f: + f.write(struct.pack("HHIIBBBB", height, width, cp_count, idx, padding, 0, 0, 0)) + + def write_codepoints(self, value, height, count): + with open(self.path, "ab") as f: + for _ in range(height * count): + f.write(struct.pack("Q", value)) + + +font = FontFile("01_bad_magic.zmfnt") +font.write_file_header(BAD_MAGIC) + +# height, width and number of codepoints out of bounds +font = FontFile("02_variant_invalid.zmfnt") +font.write_file_header(GOOD_MAGIC) +font.write_bm_header(201, 65, 256, 0, 2) + +# mismatch between number of codepoints specified in header and actually stored ones +font = FontFile("03_missing_cps.zmfnt") +font.write_file_header(GOOD_MAGIC) +offs = 0 +for _ in range(NUM_FONT_SIZES): + font.write_bm_header(10, 10, 10, offs, 2) + offs += 10 * 10 +for _ in range(NUM_FONT_SIZES): + font.write_codepoints(1, 10, 9) + +font = FontFile("04_valid.zmfnt") +font.write_file_header(GOOD_MAGIC) +offs = 0 +for i in range(NUM_FONT_SIZES): + font.write_bm_header(10 + i, 10 + i, 10, offs, 2) + offs += 10 * (10 + i) +for i in range(NUM_FONT_SIZES): + font.write_codepoints(i, 10 + i, 10) diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 000000000..1a00d1291 --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,19 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" diff --git a/tests/zm_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 new file mode 100644 index 000000000..94004527c --- /dev/null +++ b/tests/zm_crypt.cpp @@ -0,0 +1,137 @@ +/* + * 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_crypt.h" + +TEST_CASE("JWT validation") { + std::string key = "testsecret"; + + SECTION("Valid token") { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJab25lTWluZGVyIiwidXNlciI6ImpvaG5kb2UiLCJ0eXBlIjoiYWNjZXNzIiwiaWF0IjoxMjM0fQ.94WPmBAVl_83KCI9B3Jq9sNpoOdi0Hm1dR4sc6MCPUA"; + std::pair result = verifyToken(token, key); + + REQUIRE(result.first == "johndoe"); + REQUIRE(result.second == 1234); + } + + SECTION("Invalid signature") { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJab25lTWluZGVyIiwidXNlciI6ImpvaG5kb2UiLCJ0eXBlIjoiYWNjZXNzIiwiaWF0IjoxMjM0fQ.DhviT6RkDLmbXh5F9zM4l0VbWNPCuKptF6fORv1lBlA"; + std::pair result = verifyToken(token, key); + + REQUIRE(result.first == ""); + REQUIRE(result.second == 0); + } + + SECTION("Missing user claim") { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJab25lTWluZGVyIiwidHlwZSI6ImFjY2VzcyIsImlhdCI6MTIzNH0.mfi3ZHnqUAPUh5ECxDIkAM9WW9a8HbKrP73LC3yYJmw"; + std::pair result = verifyToken(token, key); + + REQUIRE(result.first == ""); + REQUIRE(result.second == 0); + } + + SECTION("Missing type claim") { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJab25lTWluZGVyIiwidXNlciI6ImpvaG5kb2UiLCJpYXQiOjEyMzR9.D4Irs1gHfzO4psRY2xsOdClTg-Sp1kM__mmfNLs7CII"; + std::pair result = verifyToken(token, key); + + REQUIRE(result.first == ""); + REQUIRE(result.second == 0); + } + + SECTION("Wrong type claim") { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJab25lTWluZGVyIiwidXNlciI6ImpvaG5kb2UiLCJ0eXBlIjoid3JvbmciLCJpYXQiOjEyMzR9.I1Gd50J6mck05vzc_kzjaH4RNjLBaFGpOnie6-PbX28"; + std::pair result = verifyToken(token, key); + + REQUIRE(result.first == ""); + REQUIRE(result.second == 0); + } + + SECTION("Missing iat claim") { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJab25lTWluZGVyIiwidXNlciI6ImpvaG5kb2UiLCJ0eXBlIjoid3JvbmcifQ.8iUFOUKJAK5vU8JWKm8D0EOEhm1rJoIulCO11O_Tsp0"; + std::pair result = verifyToken(token, key); + + REQUIRE(result.first == ""); + REQUIRE(result.second == 0); + } +} + +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 b60996a78..f20ee6183 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 @@ -79,11 +87,7 @@ else fi; if [ "$DISTROS" == "" ]; then - if [ "$RELEASE" != "" ]; then - DISTROS="xenial,bionic,disco,eoan,trusty" - else - DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`; - fi; + DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`; echo "Defaulting to $DISTROS for distribution"; else echo "Building for $DISTROS"; @@ -108,42 +112,6 @@ else echo "Defaulting to ZoneMinder upstream git" GITHUB_FORK="ZoneMinder" fi; - if [ "$SNAPSHOT" == "stable" ]; then - if [ "$BRANCH" == "" ]; then - BRANCH=$(git describe --tags $(git rev-list --tags --max-count=1)); - echo "Latest stable branch is $BRANCH"; - fi; - else - if [ "$BRANCH" == "" ]; then - echo "Defaulting to master branch"; - BRANCH="master"; - fi; - if [ "$SNAPSHOT" == "NOW" ]; then - SNAPSHOT=`date +%Y%m%d%H%M%S`; - else - if [ "$SNAPSHOT" == "CURRENT" ]; then - SNAPSHOT="`date +%Y%m%d.`$(git rev-list ${versionhash}..HEAD --count)" - fi; - fi; - fi; -fi - -if [ "$PPA" == "" ]; then - if [ "$RELEASE" != "" ]; then - # We need to use our official tarball for the original source, so grab it and overwrite our generated one. - IFS='.' read -r -a VERSION <<< "$RELEASE" - if [ "${VERSION[0]}.${VERSION[1]}" == "1.30" ]; then - PPA="ppa:iconnor/zoneminder-stable" - else - PPA="ppa:iconnor/zoneminder-${VERSION[0]}.${VERSION[1]}" - fi; - else - if [ "$BRANCH" == "" ]; then - PPA="ppa:iconnor/zoneminder-master"; - else - PPA="ppa:iconnor/zoneminder-$BRANCH"; - fi; - fi; fi; # Instead of cloning from github each time, if we have a fork lying around, update it and pull from there instead. @@ -153,11 +121,8 @@ if [ ! -d "${GITHUB_FORK}_zoneminder_release" ]; then cd "${GITHUB_FORK}_ZoneMinder.git" echo "git pull..." git pull - echo "git checkout $BRANCH" - git checkout $BRANCH - echo "git pull..." - git pull cd ../ + echo "git clone ${GITHUB_FORK}_ZoneMinder.git ${GITHUB_FORK}_zoneminder_release" git clone "${GITHUB_FORK}_ZoneMinder.git" "${GITHUB_FORK}_zoneminder_release" else @@ -170,14 +135,59 @@ else fi; cd "${GITHUB_FORK}_zoneminder_release" - git checkout $BRANCH -cd ../ -VERSION=`cat ${GITHUB_FORK}_zoneminder_release/version` +if [ "$SNAPSHOT" == "stable" ]; then + 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; + fi + echo "Latest stable branch is $BRANCH"; + fi; +else + if [ "$BRANCH" == "" ]; then + echo "Defaulting to master branch"; + BRANCH="master"; + fi; + if [ "$SNAPSHOT" == "NOW" ]; then + SNAPSHOT=`date +%Y%m%d%H%M%S`; + else + if [ "$SNAPSHOT" == "CURRENT" ]; then + # git the latest (short) commit hash of the version file + versionhash=$(git log -n1 --pretty=format:%h version) + # Number of commits since the version file was last changed + numcommits=$(git rev-list ${versionhash}..HEAD --count) + SNAPSHOT="`date +%Y%m%d.`$(git rev-list ${versionhash}..HEAD --count)" + fi; + fi; +fi; + + +echo "git checkout $BRANCH" +git checkout $BRANCH +if [ $? -ne 0 ]; then + echo "Failed to switch to branch." + exit 1; +fi; +echo "git pull..." +git pull +# Grab the ZoneMinder version from the contents of the version file +VERSION=$(cat version) if [ -z "$VERSION" ]; then exit 1; fi; +IFS='.' read -r -a VERSION_PARTS <<< "$VERSION" + +cd ../ + if [ "$SNAPSHOT" != "stable" ] && [ "$SNAPSHOT" != "" ]; then VERSION="$VERSION~$SNAPSHOT"; fi; @@ -207,7 +217,13 @@ rm -rf .git rm .gitignore cd ../ -if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then + +if [ -e "$DIRECTORY.orig.tar.gz" ]; then + read -p "$DIRECTORY.orig.tar.gz exists, overwrite it? [Y/n]" + if [[ "$REPLY" == "" || "$REPLY" == [yY] ]]; then + tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig + fi; +else tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig fi; @@ -220,14 +236,10 @@ IFS=',' ;for DISTRO in `echo "$DISTROS"`; do fi; # Generate Changlog - if [ "$DISTRO" == "trusty" ] || [ "$DISTRO" == "precise" ]; then - cp -Rpd distros/ubuntu1204 debian - else - if [ "$DISTRO" == "wheezy" ]; then - cp -Rpd distros/debian debian - else - cp -Rpd distros/ubuntu1604 debian - fi; + if [ "$DISTRO" == "beowulf" ]; then + cp -Rpd distros/beowulf debian + else + cp -Rpd distros/ubuntu2004 debian fi; if [ "$DEBEMAIL" != "" ] && [ "$DEBFULLNAME" != "" ]; then @@ -277,30 +289,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; @@ -316,7 +335,7 @@ EOF read -p "Do you want to upload this binary to zmrepo? (y/N)" if [[ $REPLY == [yY] ]]; then if [ "$RELEASE" != "" ]; then - scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/stable/mini-dinstall/incoming/" + scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/release-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}/mini-dinstall/incoming/" else if [ "$BRANCH" == "" ]; then scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/master/mini-dinstall/incoming/" @@ -328,16 +347,34 @@ EOF fi; else SC="zoneminder_${VERSION}-${DISTRO}${PACKAGE_VERSION}_source.changes"; + if [ "$PPA" == "" ]; then + if [ "$RELEASE" != "" ]; then + # We need to use our official tarball for the original source, so grab it and overwrite our generated one. + if [ "${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" == "1.30" ]; then + PPA="ppa:iconnor/zoneminder-stable" + else + PPA="ppa:iconnor/zoneminder-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" + fi; + else + if [ "$BRANCH" == "" ]; then + PPA="ppa:iconnor/zoneminder-master"; + else + PPA="ppa:iconnor/zoneminder-$BRANCH"; + fi; + fi; + fi; dput="Y"; if [ "$INTERACTIVE" != "no" ]; then - read -p "Ready to dput $SC to $PPA ? Y/N..."; - if [[ "$REPLY" == [yY] ]]; then + read -p "Ready to dput $SC to $PPA ? Y/n..."; + 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/mk_bigfont.pl b/utils/mk_bigfont.pl index 41ebb34bf..96e6881c3 100755 --- a/utils/mk_bigfont.pl +++ b/utils/mk_bigfont.pl @@ -48,18 +48,18 @@ while (my $line = ) { $in_head-- if $line =~ /^$/ and $in_head; next while $in_head; unless ($line =~ /^\s+(0x..), \/\* (........)/) { - $line =~ s/static unsigned char fontdata/static unsigned int bigfontdata/; + #$line =~ s/static unsigned char fontdata/static unsigned int bigfontdata/; print $line; next; } my $code = $1; my $bincode = $2; $bincode = "$1$1$2$2$3$3$4$4$5$5$6$6$7$7$8$8" if $bincode =~ /(.)(.)(.)(.)(.)(.)(.)(.)$/; - $bincode =~ s/ /1/g; - my $intcode = unpack("N", pack("B32", substr("0" x 32 . $bincode, -32))); - my $hexcode = sprintf("%#x", $intcode); - $hexcode =~ s/^0$/0x0/; - $bincode =~ s/1/ /g; + #$bincode =~ s/ /1/g; + #my $intcode = unpack("N", pack("B32", substr("0" x 32 . $bincode, -32))); + #my $hexcode = sprintf("%#x", $intcode); + #$hexcode =~ s/^0$/0x0/; + #$bincode =~ s/1/ /g; print sprintf("\t%10s, /* %s */\n", $hexcode, $bincode); print sprintf("\t%10s, /* %s */\n", $hexcode, $bincode); } diff --git a/utils/packpack/rsync_xfer.sh b/utils/packpack/rsync_xfer.sh index c4b6f93e1..5078194a4 100755 --- a/utils/packpack/rsync_xfer.sh +++ b/utils/packpack/rsync_xfer.sh @@ -18,7 +18,16 @@ for CMD in sshfs rsync find fusermount mkdir; do done if [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbian" ]; then - targetfolder="debian/master/mini-dinstall/incoming" + if [ "${RELEASE}" != "" ]; then + IFS='.' read -r -a VERSION_PARTS <<< "$RELEASE" + if [ "${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" == "1.30" ]; then + targetfolder="debian/release/mini-dinstall/incoming" + else + targetfolder="debian/release-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}/mini-dinstall/incoming" + fi + else + targetfolder="debian/master/mini-dinstall/incoming" + fi else targetfolder="travis" fi @@ -27,8 +36,8 @@ echo echo "Target subfolder set to $targetfolder" echo -echo "Running \$(rsync -v -e 'ssh -vvv' build/*.{rpm,deb,dsc,tar.xz,changes} zmrepo@zmrepo.zoneminder.com:${targetfolder}/ 2>&1)" -rsync -v --ignore-missing-args --exclude 'external-repo.noarch.rpm' -e 'ssh -vvv' build/*.{rpm,deb,dsc,tar.xz,changes} zmrepo@zmrepo.zoneminder.com:${targetfolder}/ 2>&1 +echo "Running \$(rsync -v -e 'ssh -vvv' build/*.{rpm,deb,dsc,tar.xz,buildinfo,changes} zmrepo@zmrepo.zoneminder.com:${targetfolder}/ 2>&1)" +rsync -v --ignore-missing-args --exclude 'external-repo.noarch.rpm' -e 'ssh -v' build/*.{rpm,deb,dsc,tar.xz,buildinfo,changes} zmrepo@zmrepo.zoneminder.com:${targetfolder}/ 2>&1 if [ "$?" -eq 0 ]; then echo echo "Files copied successfully." diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh index e6b68f66d..9737c794a 100755 --- a/utils/packpack/startpackpack.sh +++ b/utils/packpack/startpackpack.sh @@ -1,4 +1,7 @@ #!/bin/bash + +set -o pipefail + # packpack setup file for the ZoneMinder project # Written by Andrew Bauer @@ -9,7 +12,7 @@ # General sanity checks checksanity () { # Check to see if this script has access to all the commands it needs - for CMD in set echo curl git ln mkdir rmdir cat patch; do + for CMD in set echo curl git ln mkdir rmdir cat patch sed; do type $CMD 2>&1 > /dev/null if [ $? -ne 0 ]; then @@ -30,7 +33,7 @@ checksanity () { ARCH="x86_64" fi - if [[ "${ARCH}" != "x86_64" && "${ARCH}" != "i386" && "${ARCH}" != "armhf" ]]; then + if [[ "${ARCH}" != "x86_64" && "${ARCH}" != "i386" && "${ARCH}" != "armhf" && "${ARCH}" != "aarch64" ]]; then echo echo "ERROR: Unsupported architecture specified \"${ARCH}\"." echo @@ -91,12 +94,12 @@ commonprep () { # The rpm specfile requires we download each submodule as a tarball then manually move it into place # Might as well do this for Debian as well, rather than git submodule init - CRUDVER="3.1.0-zm" + CRUDVER="3.2.0" if [ -e "build/crud-${CRUDVER}.tar.gz" ]; then echo "Found existing Crud ${CRUDVER} tarball..." else echo "Retrieving Crud ${CRUDVER} submodule..." - curl -L https://github.com/ZoneMinder/crud/archive/v${CRUDVER}.tar.gz > build/crud-${CRUDVER}.tar.gz + curl -L https://github.com/FriendsOfCake/crud/archive/v${CRUDVER}.tar.gz > build/crud-${CRUDVER}.tar.gz if [ $? -ne 0 ]; then echo "ERROR: Crud tarball retreival failed..." exit 1 @@ -114,6 +117,18 @@ commonprep () { exit 1 fi fi + + RTSPVER="eab32851421ffe54fec0229c3efc44c642bc8d46" + 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 @@ -134,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 @@ -150,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 @@ -197,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 @@ -250,6 +275,13 @@ execpackpack () { else packpack/packpack $parms fi + + if [ $? -ne 0 ]; then + echo + echo "ERROR: An error occurred while executing packpack." + echo + exit 1 + fi } # Check for connectivity with the deploy target host @@ -298,6 +330,11 @@ if [ "${OS}" == "el" ] || [ "${OS}" == "fedora" ]; then commonprep echo "Begin Redhat build..." + # Newer Redhat distros use dnf package manager rather than yum + if [ "${DIST}" -gt "7" ]; then + sed -i 's\yum\dnf\' utils/packpack/redhat_package.mk + fi + setrpmpkgname ln -sfT distros/redhat rpm @@ -332,12 +369,10 @@ elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbia setdebpkgname movecrud - if [ "${DIST}" == "trusty" ] || [ "${DIST}" == "precise" ]; then - ln -sfT distros/ubuntu1204 debian - elif [ "${DIST}" == "wheezy" ]; then - ln -sfT distros/debian debian - else - ln -sfT distros/ubuntu1604 debian + 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 fi setdebchangelog @@ -346,7 +381,7 @@ elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbia execpackpack # Try to install and run the newly built zoneminder package - if [ "${OS}" == "ubuntu" ] && [ "${DIST}" == "xenial" ] && [ "${ARCH}" == "x86_64" ] && [ "${TRAVIS}" == "true" ]; then + if [ "${OS}" == "ubuntu" ] && [ "${DIST}" == "bionic" ] && [ "${ARCH}" == "x86_64" ] && [ "${TRAVIS}" == "true" ]; then echo "Begin Deb package installation..." install_deb fi diff --git a/utils/resample_event_video.sh b/utils/resample_event_video.sh new file mode 100755 index 000000000..845216cc0 --- /dev/null +++ b/utils/resample_event_video.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +FFMPEG=`which ffmpeg`; + +if [ -z "$FFMPEG" ]; then + echo "You must install the ffmpeg package. Try sudo apt-get install ffmpeg"; + exit; +fi + +for i in "$@" +do +case $i in + -r=*|--rate=*) + FPS="${i#*=}" + shift + ;; + -f=*|--file=*) + VIDEO_FILE="${i#*=}" + shift + ;; + + *) + EVENT_PATH="${i#*=}" + shift + ;; +esac +done + +if [ -z "$EVENT_PATH" ]; then + echo "You must specify the path to the event."; + exit 255 +fi; + +if [ -z "$FPS" ]; then + echo "You must specify the new fps."; + exit 255 +fi; + +$FFMPEG -i "$EVENT_PATH/$VIDEO_FILE" -filter:v fps=fps=$FPS "$EVENT_PATH/output.mp4" +echo $? +if [ $? -eq 0 ]; then + mv "$EVENT_PATH/output.mp4" "$EVENT_PATH/$VIDEO_FILE" +fi; + +exit 0; diff --git a/utils/travis/bootstrap-zm.sh b/utils/travis/bootstrap-zm.sh deleted file mode 100644 index 397a8f90e..000000000 --- a/utils/travis/bootstrap-zm.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -x - -set -e -set -o pipefail -set -x - -with_timestamps() { - while read -r line; do - echo -e "$(date +%T)\t$line"; - done -} - - -bootstrap_zm() { - - if [ "$ZM_BUILDMETHOD" = "autotools" ]; then libtoolize --force; fi - if [ "$ZM_BUILDMETHOD" = "autotools" ]; then aclocal; fi - if [ "$ZM_BUILDMETHOD" = "autotools" ]; then autoheader; fi - if [ "$ZM_BUILDMETHOD" = "autotools" ]; then automake --force-missing --add-missing; fi - if [ "$ZM_BUILDMETHOD" = "autotools" ]; then autoconf; fi - - mysql -uroot -e "CREATE DATABASE IF NOT EXISTS zm" - mysql -uroot -e "GRANT ALL ON zm.* TO 'zmuser'@'localhost' IDENTIFIED BY 'zmpass'"; - mysql -uroot -e "FLUSH PRIVILEGES" - mysql -uzmuser -pzmpass < ${TRAVIS_BUILD_DIR}/db/zm_create.sql - -} - -bootstrap_zm | with_timestamps diff --git a/utils/travis/build-zm.sh b/utils/travis/build-zm.sh deleted file mode 100644 index 2d758f61b..000000000 --- a/utils/travis/build-zm.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -set -e -set -o pipefail - -with_timestamps() { - while read -r line; do - echo -e "$(date +%T)\t$line"; - done -} - -cd $TRAVIS_BUILD_DIR - -build_zm() { - - if [ "$ZM_BUILDMETHOD" = "autotools" ]; then - ./configure --prefix=/usr --with-libarch=lib/$DEB_HOST_GNU_TYPE --host=$DEB_HOST_GNU_TYPE --build=$DEB_BUILD_GNU_TYPE --with-mysql=/usr --with-ffmpeg=/usr --with-webdir=/usr/share/zoneminder/www --with-cgidir=/usr/libexec/zoneminder/cgi-bin --with-webuser=www-data --with-webgroup=www-data --enable-crashtrace=yes --disable-debug --enable-mmap=yes ZM_SSL_LIB=openssl - fi - - if [ "$ZM_BUILDMETHOD" = "cmake" ]; then - cmake -DCMAKE_INSTALL_PREFIX="/usr" - fi - - make - sudo make install - - if [ "$ZM_BUILDMETHOD" = "cmake" ]; then - sudo ./zmlinkcontent.sh - fi - -} - -build_zm | with_timestamps diff --git a/utils/travis/install-deps.sh b/utils/travis/install-deps.sh deleted file mode 100644 index d7981cb5a..000000000 --- a/utils/travis/install-deps.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -set -e -set -o pipefail - -with_timestamps() { - while read -r line; do - echo -e "$(date +%T)\t$line"; - done -} - - -install_deps() { - - sudo apt-get update -qq - sudo apt-get install -y -qq zlib1g-dev apache2 mysql-server php5 php5-mysql build-essential libmysqlclient-dev libssl-dev libbz2-dev libpcre3-dev libdbi-perl libarchive-zip-perl libdate-manip-perl libdevice-serialport-perl libmime-perl libwww-perl libdbd-mysql-perl libsys-mmap-perl yasm automake autoconf cmake libjpeg-turbo8-dev apache2-mpm-prefork libapache2-mod-php5 php5-cli libtheora-dev libvorbis-dev libvpx-dev libx264-dev 2>&1 > /dev/null - -} - -install_deps | with_timestamps diff --git a/utils/travis/install-ffmpeg.sh b/utils/travis/install-ffmpeg.sh deleted file mode 100644 index c2a2e9733..000000000 --- a/utils/travis/install-ffmpeg.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -set -e - - -git clone --depth=10 --branch=master git://source.ffmpeg.org/ffmpeg.git -cd ffmpeg -./configure --enable-shared --enable-swscale --enable-gpl --enable-libx264 --enable-libvpx --enable-libvorbis --enable-libtheora -make -j `grep processor /proc/cpuinfo|wc -l` -sudo make install -sudo make install-libs diff --git a/utils/travis/run-tests.sh b/utils/travis/run-tests.sh deleted file mode 100644 index c3bb392d9..000000000 --- a/utils/travis/run-tests.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -set -e -set -o pipefail - -with_timestamps() { - while read -r line; do - echo -e "$(date +%T)\t$line"; - done -} - -run_tests() { - mysql -uzmuser -pzmpass zm < ../../db/test.monitor.sql - sudo zmu -l - sudo zmc -m1 & - sudo zma -m1 & - sudo zmu -l - sudo grep ERR /var/log/syslog - sudo zmpkg.pl start - sudo zmfilter.pl -f purgewhenfull -} - -run_tests | with_timestamps diff --git a/utils/zm-alarm.pl b/utils/zm-alarm.pl index d68c82663..92171db99 100755 --- a/utils/zm-alarm.pl +++ b/utils/zm-alarm.pl @@ -16,7 +16,7 @@ my @monitors; my $dbh = zmDbConnect(); my $sql = "SELECT * FROM Monitors - WHERE find_in_set( Function, 'Modect,Mocord,Nodect' )". + WHERE find_in_set( `Function`, 'Modect,Mocord,Nodect' )". ( $Config{ZM_SERVER_ID} ? 'AND ServerId=?' : '' ) ; diff --git a/version b/version index c0bd57583..f951feb88 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.33.16 +1.37.11 diff --git a/web/CMakeLists.txt b/web/CMakeLists.txt index b3d097739..9676f1cfa 100644 --- a/web/CMakeLists.txt +++ b/web/CMakeLists.txt @@ -2,27 +2,18 @@ # Process the api subdirectory add_subdirectory(api) -# Process the tools/mootools subdirectory -add_subdirectory(tools/mootools) - # Create files from the .in files configure_file(includes/config.php.in "${CMAKE_CURRENT_BINARY_DIR}/includes/config.php" @ONLY) # Install the web files -install(DIRECTORY vendor api ajax css fonts graphics includes js lang skins tools views DESTINATION "${ZM_WEBDIR}" PATTERN "*.in" EXCLUDE PATTERN "*Make*" EXCLUDE PATTERN "*cmake*" EXCLUDE) +install(DIRECTORY vendor api ajax css fonts graphics includes js lang skins views DESTINATION "${ZM_WEBDIR}" PATTERN "*.in" EXCLUDE PATTERN "*Make*" EXCLUDE PATTERN "*cmake*" EXCLUDE) install(FILES index.php robots.txt DESTINATION "${ZM_WEBDIR}") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/includes/config.php" DESTINATION "${ZM_WEBDIR}/includes") # Install the api config files (if its not in the source directory) if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/core.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/database.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/bootstrap.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/lib/Cake/bootstrap.php" DESTINATION "${ZM_WEBDIR}/api/lib/Cake") -endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - -# Install the mootools symlinks (if its not in the source directory) -if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tools/mootools/mootools-core.js" DESTINATION "${ZM_WEBDIR}/tools/mootools") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tools/mootools/mootools-more.js" DESTINATION "${ZM_WEBDIR}/tools/mootools") -endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/core.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/database.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/bootstrap.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/lib/Cake/bootstrap.php" DESTINATION "${ZM_WEBDIR}/api/lib/Cake") +endif() diff --git a/web/ajax/add_monitors.php b/web/ajax/add_monitors.php index de24cdfac..4a6809cf9 100644 --- a/web/ajax/add_monitors.php +++ b/web/ajax/add_monitors.php @@ -1,14 +1,22 @@ set(array( 'StorageId' => 1, 'ServerId' => 'auto', - 'Function' => 'Record', + 'Function' => 'Mocord', 'Type' => 'Ffmpeg', 'Enabled' => '1', 'Colour' => '4', // 32bit - 'PreEventCount' => 0, + 'ImageBufferCount' => '20', + 'WarmupCount' => '0', + 'PreEventCount' => '0', + 'StreamReplayBuffer' => '0', + 'SaveJPEGs' => '4', + 'VideoWriter' => '1', + 'MaxFPS' => '20', + 'AlarmMaxFPS' => '20', ) ); function probe( &$url_bits ) { @@ -19,11 +27,11 @@ function probe( &$url_bits ) { $cam_list_html = file_get_contents('http://'.$url_bits['host'].':5000/monitoring/'); if ( $cam_list_html ) { - ZM\Logger::Debug("Have content at port 5000/monitoring"); + ZM\Debug("Have content at port 5000/monitoring"); $matches_count = preg_match_all( '/([^<]+)<\/a>/', $cam_list_html, $cam_list ); - ZM\Logger::Debug(print_r($cam_list,true)); + ZM\Debug(print_r($cam_list,true)); } if ( $matches_count ) { for( $index = 0; $index < $matches_count; $index ++ ) { @@ -33,7 +41,7 @@ function probe( &$url_bits ) { if ( ! isset($new_stream['scheme'] ) ) $new_stream['scheme'] = 'http'; $available_streams[] = $new_stream; -ZM\Logger::Debug("Have new stream " . print_r($new_stream,true) ); +ZM\Debug("Have new stream " . print_r($new_stream,true) ); } } else { ZM\Info('No matches'); @@ -46,18 +54,18 @@ if ( 0 ) { SOL_SOCKET, // socket level SO_SNDTIMEO, // timeout option array( - "sec"=>0, // Timeout in seconds - "usec"=>500 // I assume timeout in microseconds + 'sec'=>0, // Timeout in seconds + 'usec'=>500 // I assume timeout in microseconds ) ); $new_stream = null; -Info("Testing connection to " . $url_bits['host'].':'.$port); + Info('Testing connection to '.$url_bits['host'].':'.$port); if ( socket_connect( $socket, $url_bits['host'], $port ) ) { $new_stream = $url_bits; // make a copy $new_stream['port'] = $port; } else { socket_close($socket); - ZM\Info("No connection to ".$url_bits['host'] . " on port $port"); + ZM\Info('No connection to '.$url_bits['host'].' on port '.$port); continue; } if ( $new_stream ) { @@ -92,10 +100,10 @@ Info("Testing connection to " . $url_bits['host'].':'.$port); } foreach ( $available_streams as &$stream ) { # check for existence in db. - $stream['url'] = unparse_url( $stream, array('path'=>'/','query'=>'action=stream') ); - $monitors = ZM\Monitor::find( array('Path'=>$stream['url']) ); + $stream['url'] = unparse_url($stream, array('path'=>'/','query'=>'action=stream')); + $monitors = ZM\Monitor::find(array('Path'=>$stream['url'])); if ( count($monitors) ) { - ZM\Info("Found monitors matching " . $stream['url'] ); + ZM\Info('Found monitors matching ' . $stream['url'] ); $stream['Monitor'] = $monitors[0]; if ( isset( $stream['Width'] ) and ( $stream['Monitor']->Width() != $stream['Width'] ) ) { $stream['Warning'] .= 'Monitor width ('.$stream['Monitor']->Width().') and stream width ('.$stream['Width'].") do not match!\n"; @@ -106,11 +114,11 @@ Info("Testing connection to " . $url_bits['host'].':'.$port); } else { $stream['Monitor'] = clone $defaultMonitor; if ( isset($stream['Width']) ) { - $stream['Monitor']->Width( $stream['Width'] ); - $stream['Monitor']->Height( $stream['Height'] ); + $stream['Monitor']->Width($stream['Width']); + $stream['Monitor']->Height($stream['Height']); } if ( isset($stream['Name']) ) { - $stream['Monitor']->Name( $stream['Name'] ); + $stream['Monitor']->Name($stream['Name']); } } // Monitor found or not } // end foreach Stream @@ -121,16 +129,16 @@ Info("Testing connection to " . $url_bits['host'].':'.$port); return $available_streams; } // end function probe -if ( canEdit( 'Monitors' ) ) { +if ( canEdit('Monitors') ) { switch ( $_REQUEST['action'] ) { case 'probe' : { $available_streams = array(); $url_bits = null; - if ( preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+)/', $_REQUEST['url'] ) ) { - $url_bits = array( 'host'=>$_REQUEST['url'] ); + if ( preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+)/', $_REQUEST['url']) ) { + $url_bits = array('host'=>$_REQUEST['url']); } else { - $url_bits = parse_url( $_REQUEST['url'] ); + $url_bits = parse_url($_REQUEST['url']); } if ( 0 ) { @@ -147,13 +155,13 @@ if ( 0 ) { } if ( ! $url_bits ) { - ajaxError("The given URL was too malformed to parse."); + ajaxError('The given URL was too malformed to parse.'); return; } - $available_streams = probe( $url_bits ); + $available_streams = probe($url_bits); - ajaxResponse( array('Streams'=>$available_streams) ); + ajaxResponse(array('Streams'=>$available_streams)); return; } // end case url_probe case 'import': @@ -161,16 +169,16 @@ if ( 0 ) { $file = $_FILES['import_file']; - if ($file["error"] > 0) { - ajaxError($file["error"]); + if ( $file['error'] > 0 ) { + ajaxError($file['error']); return; } else { - $filename = $file["name"]; + $filename = $file['name']; - $available_streams = array(); + $available_streams = array(); $row = 1; - if (($handle = fopen($file['tmp_name'], 'r')) !== FALSE) { - while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) { + if ( ($handle = fopen($file['tmp_name'], 'r')) !== FALSE ) { + while ( ($data = fgetcsv($handle, 1000, ',')) !== FALSE ) { $name = $data[0]; $url = $data[1]; $group = $data[2]; @@ -178,16 +186,16 @@ if ( 0 ) { $url_bits = null; if ( preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+)/', $url) ) { - $url_bits = array( 'host'=>$url, 'scheme'=>'http' ); + $url_bits = array('host'=>$url, 'scheme'=>'http'); } else { - $url_bits = parse_url( $url ); + $url_bits = parse_url($url); } if ( ! $url_bits ) { ZM\Info("Bad url, skipping line $name $url $group"); continue; } - $available_streams += probe( $url_bits ); + $available_streams += probe($url_bits); //$url_bits['url'] = unparse_url( $url_bits ); //$url_bits['Monitor'] = $defaultMonitor; @@ -197,23 +205,19 @@ if ( 0 ) { } // end while rows fclose($handle); - ajaxResponse( array('Streams'=>$available_streams) ); + ajaxResponse(array('Streams'=>$available_streams)); } else { - ajaxError("Uploaded file does not exist"); + ajaxError('Uploaded file does not exist'); return; } - } } // end case import default: - { - ZM\Warning("unknown action " . $_REQUEST['action'] ); - } // end ddcase default - } + ZM\Warning('unknown action '.$_REQUEST['action']); + } // end switch action } else { - ZM\Warning("Cannot edit monitors" ); + ZM\Warning('Cannot edit monitors'); } -ajaxError( 'Unrecognised action or insufficient permissions' ); - +ajaxError('Unrecognised action '.$_REQUEST['action'].' or insufficient permissions for user ' . $user['Username']); ?> diff --git a/web/ajax/console.php b/web/ajax/console.php index 1a5919b58..ae8a60b15 100644 --- a/web/ajax/console.php +++ b/web/ajax/console.php @@ -1,35 +1,33 @@ beginTransaction(); - $dbConn->exec('LOCK TABLES Monitors WRITE'); - for ( $i = 0; $i < count($monitor_ids); $i += 1 ) { - $monitor_id = $monitor_ids[$i]; - $monitor_id = preg_replace( '/^monitor_id-/', '', $monitor_id ); - if ( ( ! $monitor_id ) or ! ( is_integer( $monitor_id ) or ctype_digit( $monitor_id ) ) ) { - Warning("Got $monitor_id from " . $monitor_ids[$i]); - continue; - } - dbQuery('UPDATE Monitors SET Sequence=? WHERE Id=?', array($i, $monitor_id)); - } // end for each monitor_id - $dbConn->commit(); - $dbConn->exec('UNLOCK TABLES'); - - return; - } // end case sort - default: - { - ZM\Warning('unknown action ' . $_REQUEST['action']); - } // end ddcase default - } + switch ( $_REQUEST['action'] ) { + case 'sort' : + { + $monitor_ids = $_POST['monitor_ids']; + # Two concurrent sorts could generate odd sortings... so lock the table. + global $dbConn; + $dbConn->beginTransaction(); + $dbConn->exec('LOCK TABLES Monitors WRITE'); + for ( $i = 0; $i < count($monitor_ids); $i += 1 ) { + $monitor_id = $monitor_ids[$i]; + $monitor_id = preg_replace('/^monitor_id-/', '', $monitor_id); + if ( ( !$monitor_id ) or ! ( is_integer($monitor_id) or ctype_digit($monitor_id) ) ) { + Warning('Got '.$monitor_id.' from '.$monitor_ids[$i]); + continue; + } + dbQuery('UPDATE Monitors SET Sequence=? WHERE Id=?', array($i, $monitor_id)); + } // end for each monitor_id + $dbConn->commit(); + $dbConn->exec('UNLOCK TABLES'); + + return; + } // end case sort + default: + ZM\Warning('unknown action '.$_REQUEST['action']); + } } else { ZM\Warning('Cannot edit monitors'); } -ajaxError('Unrecognised action or insufficient permissions'); +ajaxError('Unrecognised action '.$_REQUEST['action'].' or insufficient permissions for user ' . $user['Username']); ?> diff --git a/web/ajax/controlcaps.php b/web/ajax/controlcaps.php new file mode 100644 index 000000000..9e655c128 --- /dev/null +++ b/web/ajax/controlcaps.php @@ -0,0 +1,33 @@ + diff --git a/web/ajax/device.php b/web/ajax/device.php new file mode 100644 index 000000000..71e76d8f7 --- /dev/null +++ b/web/ajax/device.php @@ -0,0 +1,47 @@ + diff --git a/src/zm_coord.cpp b/web/ajax/devices.php similarity index 58% rename from src/zm_coord.cpp rename to web/ajax/devices.php index df9bc0a87..25d17af49 100644 --- a/src/zm_coord.cpp +++ b/web/ajax/devices.php @@ -1,23 +1,38 @@ + diff --git a/web/ajax/event.php b/web/ajax/event.php index cb4d3d7ad..a0b9cb57a 100644 --- a/web/ajax/event.php +++ b/web/ajax/event.php @@ -1,11 +1,11 @@ $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,13 +112,17 @@ if ( canView('Events') ) { false,#detail false,#frames false,#images - $exportVideo, + true, #$exportVideo, false,#Misc $exportFormat, false#,#Compress #$exportStructure ) ) { - ajaxResponse(array('exportFile'=>$exportFile,'exportFormat'=>$exportFormat, 'connkey'=>(isset($_REQUEST['connkey'])?$_REQUEST['connkey']:''))); + ajaxResponse(array( + 'exportFile'=>$exportFile, + 'exportFormat'=>$exportFormat, + 'connkey'=>(isset($_REQUEST['connkey'])?$_REQUEST['connkey']:'') + )); } else { ajaxError('Export Failed'); } @@ -145,7 +157,7 @@ if ( canEdit('Events') ) { break; case 'delete' : $Event = new ZM\Event($_REQUEST['id']); - if ( ! $Event->Id() ) { + if ( !$Event->Id() ) { ajaxResponse(array('refreshEvent'=>false, 'refreshParent'=>true, 'message'=> 'Event not found.')); } else { $Event->delete(); @@ -155,5 +167,5 @@ if ( canEdit('Events') ) { } // end switch action } // end if canEdit('Events') -ajaxError('Unrecognised action or insufficient permissions'); +ajaxError('Unrecognised action '.$_REQUEST['action'].' or insufficient permissions for user '.$user['Username']); ?> diff --git a/web/ajax/events.php b/web/ajax/events.php new file mode 100644 index 000000000..c95b404c4 --- /dev/null +++ b/web/ajax/events.php @@ -0,0 +1,304 @@ +'; + +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
'; +} else { + $eids = $_REQUEST['eids']; +} + +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']) { + $filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>$user['MonitorIds'])); +} + +// Search contains a user entered string to search on +$search = isset($_REQUEST['search']) ? $_REQUEST['search'] : ''; + +// Advanced search contains an array of "column name" => "search text" pairs +// Bootstrap table sends json_ecoded array, which we must decode +$advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], JSON_OBJECT_AS_ARRAY) : array(); + +// 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 = $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']))) { + ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']); + } else { + $offset = $_REQUEST['offset']; + } +} + +// Limit specifies the number of rows to return +// Set the default to 0 for events view, to prevent an issue with ALL pagination +$limit = 0; +if (isset($_REQUEST['limit'])) { + if ((!is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']))) { + ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']); + } else { + $limit = $_REQUEST['limit']; + } +} + +// +// MAIN LOOP +// + +switch ($task) { + case 'archive' : + foreach ($eids as $eid) archiveRequest($task, $eid); + break; + case 'unarchive' : + # The idea is that anyone can archive, but only people with Event Edit permission can unarchive.. + if (!canEdit('Events')) { + ajaxError('Insufficient permissions for user '.$user['Username']); + return; + } + foreach ($eids as $eid) archiveRequest($task, $eid); + break; + case 'delete' : + if (!canEdit('Events')) { + ajaxError('Insufficient permissions for user '.$user['Username']); + return; + } + foreach ($eids as $eid) $data[] = deleteRequest($eid); + break; + case 'query' : + $data = queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit); + break; + default : + ZM\Fatal("Unrecognised task '$task'"); +} // end switch task + +ajaxResponse($data); + +// +// FUNCTION DEFINITIONS +// + +function archiveRequest($task, $eid) { + $archiveVal = ($task == 'archive') ? 1 : 0; + dbQuery( + 'UPDATE Events SET Archived = ? WHERE Id = ?', + array($archiveVal, $eid) + ); +} + +function deleteRequest($eid) { + $message = array(); + $event = new ZM\Event($eid); + if ( !$event->Id() ) { + $message[] = array($eid=>'Event not found.'); + } else if ( $event->Archived() ) { + $message[] = array($eid=>'Event is archived, cannot delete it.'); + } else if (!$event->canEdit()) { + $message[] = array($eid=>'You do not have permission to delete event '.$event->Id()); + } else { + $event->delete(); + } + + return $message; +} + +function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit) { + $data = array( + 'total' => 0, + 'totalNotFiltered' => 0, + 'rows' => array(), + 'updated' => preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG) + ); + + $failed = !$filter->test_pre_sql_conditions(); + if ($failed) { + ZM\Debug('Pre conditions failed, not doing sql'); + return $data; + } + + // Put server pagination code here + // The table we want our data from + $table = 'Events'; + + // The names of the dB columns in the events table we are interested in + $columns = array('Id', 'MonitorId', 'StorageId', 'Name', 'Cause', 'StartDateTime', 'EndDateTime', 'Length', 'Frames', 'AlarmFrames', 'TotScore', 'AvgScore', 'MaxScore', 'Archived', 'Emailed', 'Notes', 'DiskSpace'); + + // The names of columns shown in the event view that are NOT dB columns in the database + $col_alt = array('Monitor', 'Storage'); + + if ( $sort != '' ) { + if (!in_array($sort, array_merge($columns, $col_alt))) { + ZM\Error('Invalid sort field: ' . $sort); + $sort = ''; + } else if ( $sort == 'Monitor' ) { + $sort = 'M.Name'; + } else { + $sort = 'E.'.$sort; + } + } + + $values = array(); + $likes = array(); + $where = $filter->sql()?' WHERE ('.$filter->sql().')' : ''; + + $col_str = 'E.*, M.Name AS Monitor'; + $sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id'.$where.($sort?' ORDER BY '.$sort.' '.$order:''); + if ($filter->limit() and !count($filter->pre_sql_conditions()) and !count($filter->post_sql_conditions())) { + $sql .= ' LIMIT '.$filter->limit(); + } + + $storage_areas = ZM\Storage::find(); + $StorageById = array(); + foreach ($storage_areas as $S) { + $StorageById[$S->Id()] = $S; + } + + $unfiltered_rows = array(); + $event_ids = array(); + + ZM\Debug('Calling the following sql query: ' .$sql); + $query = dbQuery($sql, $values); + if (!$query) { + 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 + + # Filter limits come before pagination limits. + if ($filter->limit() and ($filter->limit() > count($unfiltered_rows))) { + ZM\Debug("Filtering rows due to filter->limit " . count($unfiltered_rows)." limit: ".$filter->limit()); + $unfiltered_rows = array_slice($unfiltered_rows, 0, $filter->limit()); + } + + ZM\Debug('Have ' . count($unfiltered_rows) . ' events matching base filter.'); + + $filtered_rows = null; + + if (count($advsearch) or $search != '') { + $search_filter = new ZM\Filter(); + $search_filter = $search_filter->addTerm(array('cnj'=>'and', 'attr'=>'Id', 'op'=>'IN', 'val'=>$event_ids)); + + // There are two search bars in the log view, normal and advanced + // Making an exuctive decision to ignore the normal search, when advanced search is in use + // Alternatively we could try to do both + if (count($advsearch)) { + $terms = array(); + foreach ($advsearch as $col=>$text) { + $terms[] = array('cnj'=>'and', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$text); + } # end foreach col in advsearch + $terms[0]['obr'] = 1; + $terms[count($terms)-1]['cbr'] = 1; + $search_filter->addTerms($terms); + } else if ($search != '') { + $search = '%' .$search. '%'; + $terms = array(); + foreach ($columns as $col) { + $terms[] = array('cnj'=>'or', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$search); + } + $terms[0]['obr'] = 1; + $terms[0]['cnj'] = 'and'; + $terms[count($terms)-1]['cbr'] = 1; + $search_filter = $search_filter->addTerms($terms, array('obr'=>1, 'cbr'=>1, 'op'=>'OR')); + } # end if search + + $sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE '.$search_filter->sql().' ORDER BY ' .$sort. ' ' .$order; + $filtered_rows = dbFetchAll($sql); + ZM\Debug('Have ' . count($filtered_rows) . ' events matching search filter: '.$sql); + } else { + $filtered_rows = $unfiltered_rows; + } # end if search_filter->terms() > 1 + + if ($limit) { + ZM\Debug("Filtering rows due to limit " . count($filtered_rows)." offset: $offset limit: $limit"); + $filtered_rows = array_slice($filtered_rows, $offset, $limit); + } + + $returned_rows = array(); + foreach ($filtered_rows as $row) { + $event = new ZM\Event($row); + + $scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width()); + $imgSrc = $event->getThumbnailSrc(array(), '&'); + $streamSrc = $event->getStreamSrc(array( + 'mode'=>'jpeg', 'scale'=>$scale, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'replay'=>'single', 'rate'=>'400'), '&'); + + // Modify the row data as needed + $row['imgHtml'] = 'Event '.$event->Id().''; + $row['Name'] = validHtmlStr($row['Name']); + $row['Archived'] = $row['Archived'] ? translate('Yes') : translate('No'); + $row['Emailed'] = $row['Emailed'] ? translate('Yes') : translate('No'); + $row['Cause'] = validHtmlStr($row['Cause']); + $row['StartDateTime'] = strftime(STRF_FMT_DATETIME_SHORTER, strtotime($row['StartDateTime'])); + $row['EndDateTime'] = $row['EndDateTime'] ? strftime(STRF_FMT_DATETIME_SHORTER, strtotime($row['EndDateTime'])) : null; + $row['Length'] = gmdate('H:i:s', $row['Length'] ); + $row['Storage'] = ( $row['StorageId'] and isset($StorageById[$row['StorageId']]) ) ? $StorageById[$row['StorageId']]->Name() : 'Default'; + $row['Notes'] = nl2br(htmlspecialchars($row['Notes'])); + $row['DiskSpace'] = human_filesize($event->DiskSpace()); + $returned_rows[] = $row; + } # end foreach row matching search + + $data['rows'] = $returned_rows; + + # totalNotFiltered must equal total, except when either search bar has been used + $data['totalNotFiltered'] = count($unfiltered_rows); + if ( $search != '' || count($advsearch) ) { + $data['total'] = count($filtered_rows); + } else { + $data['total'] = $data['totalNotFiltered']; + } + + return $data; +} +?> diff --git a/web/ajax/frames.php b/web/ajax/frames.php new file mode 100644 index 000000000..e129200e7 --- /dev/null +++ b/web/ajax/frames.php @@ -0,0 +1,204 @@ + "search text" pairs +// Bootstrap table sends json_ecoded array, which we must decode +$advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], JSON_OBJECT_AS_ARRAY) : array(); + +// Sort specifies the name of the column to sort on +$sort = 'FrameId'; +if ( isset($_REQUEST['sort']) ) { + $sort = $_REQUEST['sort']; +} + +// Offset specifies the starting row to return, used for pagination +$offset = 0; +if ( isset($_REQUEST['offset']) ) { + if ( ( !is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']) ) ) { + ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']); + } else { + $offset = $_REQUEST['offset']; + } +} + +// Order specifies the sort direction, either asc or desc +$order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC'; + +// Limit specifies the number of rows to return +$limit = 0; +if ( isset($_REQUEST['limit']) ) { + if ( ( !is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) { + ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']); + } else { + $limit = $_REQUEST['limit']; + } +} + +// +// MAIN LOOP +// + +// Only one supported task at the moment +switch ( $task ) { + case 'query' : + $data = queryRequest($eid, $search, $advsearch, $sort, $offset, $order, $limit); + break; + default : + ZM\Fatal("Unrecognised task '$task'"); +} // end switch task + +ajaxResponse($data); + +// +// FUNCTION DEFINITIONS +// + +function queryRequest($eid, $search, $advsearch, $sort, $offset, $order, $limit) { + + $data = array( + 'total' => 0, + 'totalNotFiltered' => 0, + 'rows' => array(), + 'updated' => preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG) + ); + + // The names of the dB columns in the events table we are interested in + $columns = array('FrameId', 'Type', 'TimeStamp', 'Delta', 'Score'); + + if ( !in_array($sort, $columns) ) { + ZM\Error('Invalid sort field: ' . $sort); + $sort = 'FrameId'; + } + + $Event = new ZM\Event($eid); + $Monitor = $Event->Monitor(); + $values = array(); + $likes = array(); + $where = 'WHERE EventId = '.$eid; + + $sql = 'SELECT * FROM `Frames` '.$where.' ORDER BY '.$sort.' '.$order; + + //ZM\Debug('Calling the following sql query: ' .$sql); + + $unfiltered_rows = array(); + $frame_ids = array(); + require_once('includes/Frame.php'); + foreach ( dbFetchAll($sql, NULL, $values) as $row ) { + $frame = new ZM\Frame($row); + $frame_ids[] = $frame->Id(); + $unfiltered_rows[] = $row; + } + + ZM\Debug('Have ' . count($unfiltered_rows) . ' frames matching base filter.'); + + $filtered_rows = null; + require_once('includes/Filter.php'); + if ( count($advsearch) or $search != '' ) { + $search_filter = new ZM\Filter(); + $search_filter = $search_filter->addTerm(array('cnj'=>'and', 'attr'=>'FrameId', 'op'=>'IN', 'val'=>$frame_ids)); + + // There are two search bars in the log view, normal and advanced + // Making an exuctive decision to ignore the normal search, when advanced search is in use + // Alternatively we could try to do both + if ( count($advsearch) ) { + $terms = array(); + foreach ( $advsearch as $col=>$text ) { + $terms[] = array('cnj'=>'and', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$text); + } # end foreach col in advsearch + $terms[0]['obr'] = 1; + $terms[count($terms)-1]['cbr'] = 1; + $search_filter->addTerms($terms); + } else if ( $search != '' ) { + $search = '%' .$search. '%'; + $terms = array(); + foreach ( $columns as $col ) { + $terms[] = array('cnj'=>'or', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$search); + } + $terms[0]['obr'] = 1; + $terms[0]['cnj'] = 'and'; + $terms[count($terms)-1]['cbr'] = 1; + $search_filter = $search_filter->addTerms($terms, array('obr'=>1, 'cbr'=>1, 'op'=>'OR')); + } # end if search + + $sql = 'SELECT * FROM `Frames` WHERE '.$search_filter->sql().' ORDER BY ' .$sort. ' ' .$order; + $filtered_rows = dbFetchAll($sql); + ZM\Debug('Have ' . count($filtered_rows) . ' frames matching search filter.'); + } else { + $filtered_rows = $unfiltered_rows; + } # end if search_filter->terms() > 1 + + $returned_rows = array(); + foreach ( array_slice($filtered_rows, $offset, $limit) as $row ) { + if ( ZM_WEB_LIST_THUMBS ) { + + # Build the path to the potential analysis image + $analImage = sprintf('%0'.ZM_EVENT_IMAGE_DIGITS.'d-analyse.jpg', $row['FrameId']); + $analPath = $Event->Path().'/'.$analImage; + $alarmFrame = $row['Type'] == 'Alarm'; + $hasAnalImage = $alarmFrame && file_exists($analPath) && filesize($analPath); + + # Our base img source component, which we will add on to + $base_img_src = '?view=image&fid=' .$row['Id']; + + # if an analysis images exists, use it as the thumbnail + if ( $hasAnalImage ) $base_img_src .= '&show=analyse'; + + # Build the subcomponents needed for the image source + $ratio_factor = $Monitor->ViewHeight() / $Monitor->ViewWidth(); + $thmb_width = ZM_WEB_LIST_THUMB_WIDTH ? 'width='.ZM_WEB_LIST_THUMB_WIDTH : ''; + $thmb_height = 'height="'.( ZM_WEB_LIST_THUMB_HEIGHT ? ZM_WEB_LIST_THUMB_HEIGHT : ZM_WEB_LIST_THUMB_WIDTH*$ratio_factor ) .'"'; + $thmb_fn = 'filename=' .$Event->MonitorId(). '_' .$row['EventId']. '_' .$row['FrameId']. '.jpg'; + + # Assemble the scaled and unscaled image source image source components + $img_src = join('&', array_filter(array($base_img_src, $thmb_width, $thmb_height, $thmb_fn))); + $full_img_src = join('&', array_filter(array($base_img_src, $thmb_fn))); + + # finally, we assemble the the entire thumbnail img src structure, whew + $row['Thumbnail'] = ''; + } + $returned_rows[] = $row; + } # end foreach row matching search + + $data['rows'] = $returned_rows; + + # totalNotFiltered must equal total, except when either search bar has been used + $data['totalNotFiltered'] = count($unfiltered_rows); + if ( $search != '' || count($advsearch) ) { + $data['total'] = count($filtered_rows); + } else { + $data['total'] = $data['totalNotFiltered']; + } + + return $data; +} diff --git a/web/ajax/log.php b/web/ajax/log.php index 36a29adc3..c5181e49b 100644 --- a/web/ajax/log.php +++ b/web/ajax/log.php @@ -1,457 +1,184 @@ 'web_js')); + + $string = $_POST['message']; + + $file = !empty($_POST['file']) ? preg_replace('/\w+:\/\/[\w.:]+\//', '', $_POST['file']) : ''; + if ( !empty($_POST['line']) ) { + $line = validInt($_POST['line']); + } else { + $line = NULL; + } + + $levels = array_flip(ZM\Logger::$codes); + if ( !isset($levels[$_POST['level']]) ) { + ZM\Panic('Unexpected logger level '.$_POST['level']); + } + $level = $levels[$_POST['level']]; + ZM\Logger::fetch()->logPrint($level, $string, $file, $line); + } else { + ZM\Error('Invalid log create: '.print_r($_POST, true)); + } +} + +function queryRequest() { + + // Offset specifies the starting row to return, used for pagination + $offset = 0; + if ( isset($_REQUEST['offset']) ) { + if ( ( !is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']) ) ) { + ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']); + } else { + $offset = $_REQUEST['offset']; + } + } + + // Limit specifies the number of rows to return $limit = 100; if ( isset($_REQUEST['limit']) ) { - if ( ( !is_integer($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) { - ZM\Error('Invalid value for limit ' . $_REQUEST['limit']); + if ( ( !is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) { + ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']); } else { $limit = $_REQUEST['limit']; } } - $sortField = 'TimeKey'; - if ( isset($_REQUEST['sortField']) ) { - if ( !in_array($_REQUEST['sortField'], $filterFields) and ( $_REQUEST['sortField'] != 'TimeKey' ) ) { - ZM\Error('Invalid sort field ' . $_REQUEST['sortField']); - } else { - $sortField = $_REQUEST['sortField']; - } - } - $sortOrder = (isset($_REQUEST['sortOrder']) and ($_REQUEST['sortOrder'] == 'asc')) ? 'asc' : 'desc'; - $filter = isset($_REQUEST['filter']) ? $_REQUEST['filter'] : array(); + // The table we want our data from + $table = 'Logs'; - $sql = $action.' FROM Logs'; - $where = array(); - $values = array(); - if ( $minTime ) { - $where[] = 'TimeKey > ?'; - $values[] = $minTime; - } elseif ( $maxTime ) { - $where[] = 'TimeKey < ?'; - $values[] = $maxTime; + // The names of the dB columns in the log table we are interested in + $columns = array('TimeKey', 'Component', 'ServerId', 'Pid', 'Code', 'Message', 'File', 'Line'); + + // The names of columns shown in the log view that are NOT dB columns in the database + $col_alt = array('DateTime', 'Server'); + + $sort = 'TimeKey'; + if ( isset($_REQUEST['sort']) ) { + $sort = $_REQUEST['sort']; + if ( $sort == 'DateTime' ) $sort = 'TimeKey'; + } + if ( !in_array($sort, array_merge($columns, $col_alt)) ) { + ZM\Error('Invalid sort field: ' . $sort); + return; } - foreach ( $filter as $field=>$value ) { - if ( !in_array($field, $filterFields) ) { - ZM\Error("'$field' is not in valid filter fields " . print_r($filterField,true)); - continue; - } - if ( $field == 'Level' ){ - $where[] = $field.' <= ?'; - $values[] = $value; - } else { - $where[] = $field.' = ?'; - $values[] = $value; - } - } - if ( count($where) ) - $sql.= ' WHERE '.join(' AND ', $where); - $sql .= ' ORDER BY '.$sortField.' '.$sortOrder.' LIMIT '.$limit; + // Order specifies the sort direction, either asc or desc + $order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC'; - return array('sql'=>$sql, 'values'=>$values); -} # function buildLogQuery($action) + $col_str = implode(', ', $columns); + $data = array(); + $query = array(); + $query['values'] = array(); + $likes = array(); + $where = ''; + // There are two search bars in the log view, normal and advanced + // Making an exuctive decision to ignore the normal search, when advanced search is in use + // Alternatively we could try to do both + // + // Advanced search contains an array of "column name" => "search text" pairs + // Bootstrap table sends json_ecoded array, which we must decode + $advsearch = isset($_REQUEST['filter']) ? json_decode($_REQUEST['filter'], JSON_OBJECT_AS_ARRAY) : array(); + // Search contains a user entered string to search on + $search = isset($_REQUEST['search']) ? $_REQUEST['search'] : ''; + if ( count($advsearch) ) { -switch ( $_REQUEST['task'] ) { - case 'create' : - { - // Silently ignore bogus requests - if ( !empty($_POST['level']) && !empty($_POST['message']) ) { - ZM\logInit(array('id'=>'web_js')); - - $string = $_POST['message']; - - $file = !empty($_POST['file']) ? preg_replace( '/\w+:\/\/[\w.:]+\//', '', $_POST['file'] ) : ''; - if ( !empty( $_POST['line'] ) ) { - $line = validInt($_POST['line']); - } else { - $line = NULL; + foreach ( $advsearch as $col=>$text ) { + if ( !in_array($col, array_merge($columns, $col_alt)) ) { + ZM\Error("'$col' is not a searchable column name"); + continue; } - - $levels = array_flip(ZM\Logger::$codes); - if ( !isset($levels[$_POST['level']]) ) { - ZM\Panic('Unexpected logger level '.$_POST['level']); - } - $level = $levels[$_POST['level']]; - ZM\Logger::fetch()->logPrint($level, $string, $file, $line); + // Don't use wildcards on advanced search + //$text = '%' .$text. '%'; + array_push($likes, $col.' LIKE ?'); + array_push($query['values'], $text); } - ajaxResponse(); - break; + $wherevalues = $query['values']; + $where = ' WHERE (' .implode(' OR ', $likes). ')'; + + } else if ( $search != '' ) { + + $search = '%' .$search. '%'; + foreach ( $columns as $col ) { + array_push($likes, $col.' LIKE ?'); + array_push($query['values'], $search); + } + $wherevalues = $query['values']; + $where = ' WHERE (' .implode(' OR ', $likes). ')'; + } + + $query['sql'] = 'SELECT ' .$col_str. ' FROM `' .$table. '` ' .$where. ' ORDER BY ' .$sort. ' ' .$order. ' LIMIT ?, ?'; + array_push($query['values'], $offset, $limit); + + $data['totalNotFiltered'] = dbFetchOne('SELECT count(*) AS Total FROM ' .$table, 'Total'); + if ( $search != '' || count($advsearch) ) { + $data['total'] = dbFetchOne('SELECT count(*) AS Total FROM ' .$table.$where , 'Total', $wherevalues); + } else { + $data['total'] = $data['totalNotFiltered']; } - case 'delete' : - { - if ( !canEdit('System') ) - ajaxError('Insufficient permissions to delete log entries'); - $query = buildLogQuery('DELETE'); - $result = dbQuery($query['sql'], $query['values']); - ajaxResponse( array( - 'result'=>'Ok', - 'deleted'=>$result->rowCount(), - ) ); + $rows = array(); + $results = dbFetchAll($query['sql'], NULL, $query['values']); + foreach ( $results as $row ) { + $row['DateTime'] = strftime('%Y-%m-%d %H:%M:%S', intval($row['TimeKey'])); + $Server = ZM\Server::find_one(array('Id'=>$row['ServerId'])); + $row['Server'] = $Server ? $Server->Name() : ''; + // First strip out any html tags + // Second strip out all characters that are not ASCII 32-126 (yes, 126) + $row['Message'] = preg_replace('/[^\x20-\x7E]/', '', strip_tags($row['Message'])); + $rows[] = $row; } - case 'query' : - { - if ( !canView('System') ) - ajaxError('Insufficient permissions to view log entries'); - $total = dbFetchOne('SELECT count(*) AS Total FROM Logs', 'Total'); - $query = buildLogQuery('SELECT *'); + $data['rows'] = $rows; + $data['logstate'] = logState(); + $data['updated'] = preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG); - $servers = ZM\Server::find(); - $servers_by_Id = array(); - # There is probably a better way to do this. - foreach ( $servers as $server ) { - $servers_by_Id[$server->Id()] = $server; - } - - $logs = array(); - $options = array(); - - foreach ( dbFetchAll($query['sql'], NULL, $query['values']) as $log ) { - - $log['DateTime'] = strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey'])); - #Warning("TimeKey: " . $log['TimeKey'] . 'Intval:'.intval($log['TimeKey']).' DateTime:'.$log['DateTime']); - #$log['DateTime'] = preg_replace('/^\d+/', strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey'])), $log['TimeKey']); - $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; - $log['Message'] = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '', $log['Message']); - foreach( $filterFields as $field ) { - if ( !isset($options[$field]) ) - $options[$field] = array(); - $value = $log[$field]; - - if ( $field == 'Level' ) { - if ( $value <= ZM\Logger::INFO ) - $options[$field][$value] = ZM\Logger::$codes[$value]; - else - $options[$field][$value] = 'DB'.$value; - } else if ( $field == 'ServerId' ) { - $options['ServerId'][$value] = ( $value and isset($servers_by_Id[$value]) ) ? $servers_by_Id[$value]->Name() : ''; - } else if ( isset($log[$field]) ) { - $options[$field][$log[$field]] = $value; - } - } - $logs[] = $log; - } - - foreach ( $options as $field => $values ) { - asort($options[$field]); - } - - $available = count($logs); - ajaxResponse( array( - 'updated' => preg_match('/%/', DATE_FMT_CONSOLE_LONG)?strftime(DATE_FMT_CONSOLE_LONG):date(DATE_FMT_CONSOLE_LONG), - 'total' => $total, - 'available' => isset($available) ? $available : $total, - 'logs' => $logs, - 'state' => logState(), - 'options' => $options, - ) ); - break; - } - case 'export' : - { - if ( !canView('System') ) - ajaxError('Insufficient permissions to export logs'); - - $minTime = isset($_POST['minTime'])?$_POST['minTime']:NULL; - $maxTime = isset($_POST['maxTime'])?$_POST['maxTime']:NULL; - if ( !is_null($minTime) && !is_null($maxTime) && $minTime > $maxTime ) { - $tempTime = $minTime; - $minTime = $maxTime; - $maxTime = $tempTime; - } - //$limit = isset($_POST['limit'])?$_POST['limit']:1000; - $filter = isset($_POST['filter'])?$_POST['filter']:array(); - $sortField = 'TimeKey'; - if ( isset($_POST['sortField']) ) { - if ( ! in_array( $_POST['sortField'], $filterFields ) and ( $_POST['sortField'] != 'TimeKey' ) ) { - ZM\Error("Invalid sort field " . $_POST['sortField'] ); - } else { - $sortField = $_POST['sortField']; - } - } - $sortOrder = (isset($_POST['sortOrder']) and $_POST['sortOrder']) == 'asc' ? 'asc':'desc'; - - $servers = ZM\Server::find(); - $servers_by_Id = array(); - # There is probably a better way to do this. - foreach ( $servers as $server ) { - $servers_by_Id[$server->Id()] = $server; - } - - $sql = 'SELECT * FROM Logs'; - $where = array(); - $values = array(); - if ( $minTime ) { - ZM\Logger::Debug("MinTime: $minTime"); - if ( preg_match('/(.+)(\.\d+)/', $minTime, $matches) ) { - # This handles sub second precision - $minTime = strtotime($matches[1]).$matches[2]; - ZM\Logger::Debug("MinTime: $minTime"); - } else { - $minTime = strtotime($minTime); - } - $where[] = 'TimeKey >= ?'; - $values[] = $minTime; - } - if ( $maxTime ) { - if ( preg_match('/(.+)(\.\d+)/', $maxTime, $matches) ) { - $maxTime = strtotime($matches[1]).$matches[2]; - } else { - $maxTime = strtotime($maxTime); - } - $where[] = 'TimeKey <= ?'; - $values[] = $maxTime; - } - foreach ( $filter as $field=>$value ) { - if ( $value != '' ) { - if ( $field == 'Level' ) { - $where[] = $field.' <= ?'; - $values[] = $value; - } else { - $where[] = $field.' = ?'; - $values[] = $value; - } - } - } - if ( count($where) ) - $sql.= ' WHERE '.join( ' AND ', $where ); - $sql .= ' ORDER BY '.$sortField.' '.$sortOrder; - //$sql .= " limit ".dbEscape($limit); - $format = isset($_POST['format'])?$_POST['format']:'text'; - switch( $format ) { - case 'text' : - $exportExt = 'txt'; - break; - case 'tsv' : - $exportExt = 'tsv'; - break; - case 'html' : - $exportExt = 'html'; - break; - case 'xml' : - $exportExt = 'xml'; - break; - default : - ZM\Fatal("Unrecognised log export format '$format'"); - } - $exportKey = substr(md5(rand()),0,8); - $exportFile = "zm-log.$exportExt"; - if ( ! file_exists(ZM_DIR_EXPORTS) ) { - ZM\Logger::Debug('Creating ' . ZM_DIR_EXPORTS); - if ( ! mkdir(ZM_DIR_EXPORTS) ) { - ZM\Fatal("Can't create exports dir at '".ZM_DIR_EXPORTS."'"); - } - } - $exportPath = ZM_DIR_EXPORTS."/zm-log-$exportKey.$exportExt"; - ZM\Logger::Debug("Exporting to $exportPath"); - if ( !($exportFP = fopen($exportPath, 'w')) ) - ZM\Fatal("Unable to open log export file $exportPath"); - $logs = array(); - foreach ( dbFetchAll($sql, NULL, $values) as $log ) { - $log['DateTime'] = preg_replace('/^\d+/', strftime( "%Y-%m-%d %H:%M:%S", intval($log['TimeKey']) ), $log['TimeKey']); - $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; - $logs[] = $log; - } - ZM\Logger::Debug(count($logs)." lines being exported by $sql " . implode(',',$values)); - - switch( $format ) { - case 'text' : - { - foreach ( $logs as $log ) { - if ( $log['Line'] ) - fprintf( $exportFP, "%s %s[%d].%s-%s/%d [%s]\n", - $log['DateTime'], $log['Component'], $log['Pid'], $log['Code'], $log['File'], $log['Line'], $log['Message'] ); - else - fprintf( $exportFP, "%s %s[%d].%s-%s [%s]\n", - $log['DateTime'], $log['Component'], $log['Pid'], $log['Code'], $log['File'], $log['Message'] ); - } - break; - } - case 'tsv' : - { - # This line doesn't need fprintf, it could use fwrite - fprintf( $exportFP, join( "\t", - translate('DateTime'), - translate('Component'), - translate('Server'), - translate('Pid'), - translate('Level'), - translate('Message'), - translate('File'), - translate('Line') - )."\n" ); - foreach ( $logs as $log ) { - fprintf( $exportFP, "%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\n", $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line'] ); - } - break; - } - case 'html' : - { - fwrite( $exportFP, - ' - - - - '.translate('ZoneMinderLog').' - - - -

'.translate('ZoneMinderLog').'

-

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

-

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

- - - - ' ); - foreach ( $logs as $log ) { - $classLevel = $log['Level']; - if ( $classLevel < ZM\Logger::FATAL ) - $classLevel = ZM\Logger::FATAL; - elseif ( $classLevel > ZM\Logger::DEBUG ) - $classLevel = ZM\Logger::DEBUG; - $logClass = 'log-'.strtolower(ZM\Logger::$codes[$classLevel]); - fprintf( $exportFP, " \n", $logClass, $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line'] ); - } - fwrite( $exportFP, - ' -
'.translate('DateTime').''.translate('Component').''.translate('Server').''.translate('Pid').''.translate('Level').''.translate('Message').''.translate('File').''.translate('Line').'
%s%s%s%d%s%s%s%s
- - ' ); - break; - } - case 'xml' : - { - fwrite( $exportFP, - ' - - '.$_POST['selector'].'' ); - foreach ( $filter as $field=>$value ) - if ( $value != '' ) - fwrite( $exportFP, - ' - <'.strtolower($field).'>'.htmlspecialchars($value).' - ' ); - fwrite( $exportFP, - ' - '.translate('DateTime').''.translate('Component').''.translate('Pid').''.translate('Level').''.translate('Message').''.translate('File').''.translate('Line').' - - - ' ); - foreach ( $logs as $log ) { - fprintf( $exportFP, - " - %s - %s - %s - %d - %s - - %s - %d - \n", $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], utf8_decode( $log['Message'] ), $log['File'], $log['Line'] ); - } - fwrite( $exportFP, - ' - ' ); - break; - } - $exportExt = 'xml'; - break; - } - fclose( $exportFP ); - ajaxResponse( array( - 'key' => $exportKey, - 'format' => $format, - ) ); - break; - } - case 'download' : - { - if ( !canView('System') ) - ajaxError('Insufficient permissions to download logs'); - - if ( empty($_REQUEST['key']) ) - ZM\Fatal('No log export key given'); - $exportKey = $_REQUEST['key']; - if ( empty($_REQUEST['format']) ) - ZM\Fatal('No log export format given'); - $format = $_REQUEST['format']; - - switch( $format ) { - case 'text' : - $exportExt = 'txt'; - break; - case 'tsv' : - $exportExt = 'tsv'; - break; - case 'html' : - $exportExt = 'html'; - break; - case 'xml' : - $exportExt = 'xml'; - break; - default : - ZM\Fatal("Unrecognised log export format '$format'"); - } - - $exportFile = "zm-log.$exportExt"; - $exportPath = ZM_DIR_EXPORTS."/zm-log-$exportKey.$exportExt"; - - header('Pragma: public'); - header('Expires: 0'); - header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header('Cache-Control: private', false ); // required by certain browsers - header('Content-Description: File Transfer'); - header('Content-Disposition: attachment; filename="'.$exportFile.'"' ); - header('Content-Transfer-Encoding: binary'); - header('Content-Type: application/force-download'); - header('Content-Length: '.filesize($exportPath)); - readfile($exportPath); - exit(0); - break; - } + return $data; } -ajaxError('Unrecognised action or insufficient permissions'); -?> diff --git a/web/ajax/modal.php b/web/ajax/modal.php new file mode 100644 index 000000000..2794fb0af --- /dev/null +++ b/web/ajax/modal.php @@ -0,0 +1,24 @@ + diff --git a/web/ajax/modals/controlpreset.php b/web/ajax/modals/controlpreset.php new file mode 100644 index 000000000..e3cc02db3 --- /dev/null +++ b/web/ajax/modals/controlpreset.php @@ -0,0 +1,53 @@ + + diff --git a/web/ajax/modals/delconfirm.php b/web/ajax/modals/delconfirm.php new file mode 100644 index 000000000..02f742cff --- /dev/null +++ b/web/ajax/modals/delconfirm.php @@ -0,0 +1,26 @@ + + + diff --git a/web/ajax/modals/device.php b/web/ajax/modals/device.php new file mode 100644 index 000000000..5a29785ef --- /dev/null +++ b/web/ajax/modals/device.php @@ -0,0 +1,54 @@ + "", + "Name" => "New Device", + "KeyString" => "" + ); +} + +?> + diff --git a/web/skins/classic/views/donate.php b/web/ajax/modals/donate.php similarity index 56% rename from web/skins/classic/views/donate.php rename to web/ajax/modals/donate.php index 6cf873bd4..374d4e0d2 100644 --- a/web/skins/classic/views/donate.php +++ b/web/ajax/modals/donate.php @@ -18,10 +18,7 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -if ( !canEdit('System') ) { - $view = 'error'; - return; -} +if ( !canEdit('System') ) return; $options = array( 'go' => translate('DonateYes'), @@ -33,18 +30,23 @@ $options = array( 'already' => translate('DonateAlready'), ); -$focusWindow = true; - -xhtmlHeaders(__FILE__, translate('Donate')); ?> - -
- -
-
+ +
+ diff --git a/web/ajax/modals/download.php b/web/ajax/modals/download.php new file mode 100644 index 000000000..7009f8738 --- /dev/null +++ b/web/ajax/modals/download.php @@ -0,0 +1,119 @@ +'.PHP_EOL; + + $Event = new ZM\Event($eid); + if ( !$Event->Id() ) { + ZM\Error('Invalid event id'); + $result .= '
Invalid event id
'.PHP_EOL; + } else { + $result .= 'Downloading event ' . $Event->Id . '. Resulting file should be approximately ' . human_filesize( $Event->DiskSpace() ).PHP_EOL; + } + } else if ( !empty($eids) ) { + $total_size = 0; + foreach ( $eids as $eid ) { + if ( !validInt($eid) ) { + ZM\Warning("Invalid event id in eids[] $eid"); + continue; + } + $Event = new ZM\Event($eid); + $total_size += $Event->DiskSpace(); + $result .= ''.PHP_EOL; + } + unset($eid); + $result .= 'Downloading ' . count($eids) . ' events. Resulting file should be approximately ' . human_filesize($total_size).PHP_EOL; + } else { + $result .= '
There are no events found. Resulting download will be empty.
'; + } + + return $result; +} + +if ( !canView('Events') ) { + $view = 'error'; + return; +} + +$eid = isset($_REQUEST['eid']) ? $_REQUEST['eid'] : ''; +$eids = isset($_REQUEST['eids']) ? $_REQUEST['eids'] : array(); +$generated = isset($_REQUEST['generated']) ? $_REQUEST['generated'] : ''; + +$total_size = 0; +if ( isset($_SESSION['montageReviewFilter']) and !$eids ) { + # Handles montageReview filter + $eventsSql = 'SELECT E.Id, E.DiskSpace FROM Events AS E WHERE 1'; + $eventsSql .= $_SESSION['montageReviewFilter']['sql']; + $results = dbQuery($eventsSql); + while ( $event_row = dbFetchNext( $results ) ) { + array_push($eids, $event_row['Id']); + $total_size += $event_row['DiskSpace']; + } + if ( ! count($eids) ) { + ZM\Error("No events found for download using $eventsSql"); + } + #session_start(); + #unset($_SESSION['montageReviewFilter']); + #session_write_close(); +#} else { +#Debug("NO montageReviewFilter"); +} + +$exportFormat = ''; +if ( isset($_REQUEST['exportFormat']) ) { + if ( !in_array($_REQUEST['exportFormat'], array('zip', 'tar')) ) { + ZM\Error('Invalid exportFormat: '.$_REQUEST['exportFormat']); + } else { + $exportFormat = $_REQUEST['exportFormat']; + } +} + +$focusWindow = true; +$connkey = isset($_REQUEST['connkey']) ? validInt($_REQUEST['connkey']) : generateConnKey(); + +?> +
diff --git a/web/ajax/modals/enoperm.php b/web/ajax/modals/enoperm.php new file mode 100644 index 000000000..6fd2fbe23 --- /dev/null +++ b/web/ajax/modals/enoperm.php @@ -0,0 +1,24 @@ + + + diff --git a/web/ajax/modals/eventdetail.php b/web/ajax/modals/eventdetail.php new file mode 100644 index 000000000..bb2f53ecc --- /dev/null +++ b/web/ajax/modals/eventdetail.php @@ -0,0 +1,89 @@ +'; + $newEvent = dbFetchOne('SELECT E.* FROM Events AS E WHERE E.Id = ?', NULL, array($eid)); + +} elseif ( $eids ) { // Multi Event Mode + + $title = translate('Events'); + $sql = 'SELECT E.* FROM Events AS E WHERE '; + $sqlWhere = array(); + $sqlValues = array(); + foreach ( $eids as $eid ) { + $eid = validInt($eid); + $inputs .= ''; + $sqlWhere[] = 'E.Id = ?'; + $sqlValues[] = $eid; + } + unset($eid); + $sql .= join(' OR ', $sqlWhere); + foreach( dbFetchAll( $sql, NULL, $sqlValues ) as $row ) { + if ( !isset($newEvent) ) { + $newEvent = $row; + } else { + if ( $newEvent['Cause'] && $newEvent['Cause'] != $row['Cause'] ) + $newEvent['Cause'] = ''; + if ( $newEvent['Notes'] && $newEvent['Notes'] != $row['Notes'] ) + $newEvent['Notes'] = ''; + } + } + +} else { // Event Mode not specified - should we really proceed if neither eid nor eids is set? + $title = translate('Events'); +} + +?> + + diff --git a/web/ajax/modals/eventrename.php b/web/ajax/modals/eventrename.php new file mode 100644 index 000000000..f556a09b0 --- /dev/null +++ b/web/ajax/modals/eventrename.php @@ -0,0 +1,26 @@ + + diff --git a/web/ajax/modals/filterdebug.php b/web/ajax/modals/filterdebug.php new file mode 100644 index 000000000..f3a961874 --- /dev/null +++ b/web/ajax/modals/filterdebug.php @@ -0,0 +1,44 @@ + diff --git a/web/ajax/modals/function.php b/web/ajax/modals/function.php new file mode 100644 index 000000000..c47664c4b --- /dev/null +++ b/web/ajax/modals/function.php @@ -0,0 +1,101 @@ + + +
+ + +
+ + diff --git a/web/ajax/modals/group.php b/web/ajax/modals/group.php new file mode 100644 index 000000000..119149951 --- /dev/null +++ b/web/ajax/modals/group.php @@ -0,0 +1,135 @@ +Id(); +} + +function get_children($Group) { + global $children; + + $kids = array(); + if ( isset( $children[$Group->Id()] ) ) { + $kids += array_map('get_Id', $children[$Group->Id()]); + foreach ( $children[$Group->Id()] as $G ) { + foreach ( get_children($G) as $id ) { + $kids[] = $id; + } + } + } + return $kids; +} + +function parentGrpSelect($newGroup) { + $Groups = array(); + foreach ( ZM\Group::find() as $Group ) { + $Groups[$Group->Id()] = $Group; + } + + # This array is indexed by parent_id + $children = array(); + + foreach ( $Groups as $id=>$Group ) { + if ( $Group->ParentId() != null ) { + if ( ! isset( $children[$Group->ParentId()] ) ) + $children[$Group->ParentId()] = array(); + $children[$Group->ParentId()][] = $Group; + } + } + + $kids = get_children($newGroup); + if ( $newGroup->Id() ) + $kids[] = $newGroup->Id(); + $sql = 'SELECT Id,Name FROM `Groups`'.(count($kids)?' WHERE Id NOT IN ('.implode(',',array_map(function(){return '?';}, $kids)).')' : '').' ORDER BY Name'; + $options = array(''=>'None'); + + foreach ( dbFetchAll($sql, null, $kids) as $option ) { + $options[$option['Id']] = str_repeat('  ', $Groups[$option['Id']]->depth()) . $option['Name']; + } + + return htmlSelect('newGroup[ParentId]', $options, $newGroup->ParentId(), array('data-on-change'=>'configModalBtns')); +} + +function monitorList($newGroup) { + $result = ''; + + $monitors = dbFetchAll('SELECT Id,Name FROM Monitors ORDER BY Sequence ASC'); + $monitorIds = $newGroup->MonitorIds(); + foreach ( $monitors as $monitor ) { + if ( visibleMonitor($monitor['Id']) ) { + $result .= ''.PHP_EOL; + } + } + + return $result; +} + +// +// INITIAL SANITY CHECKS +// + +if ( !canEdit('Groups') ) { + $view = 'error'; + return; +} + +if ( !empty($_REQUEST['gid']) ) { + $newGroup = new ZM\Group($_REQUEST['gid']); +} else { + $newGroup = new ZM\Group(); +} + +// +// BEGIN HTML +// +?> + diff --git a/web/ajax/modals/log_export.php b/web/ajax/modals/log_export.php new file mode 100644 index 000000000..8f359bb86 --- /dev/null +++ b/web/ajax/modals/log_export.php @@ -0,0 +1,46 @@ +