diff -Nru knot-resolver-5.1.1/AUTHORS knot-resolver-5.2.1/AUTHORS --- knot-resolver-5.1.1/AUTHORS 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/AUTHORS 2020-12-09 09:44:29.000000000 +0000 @@ -13,6 +13,7 @@ Ali Asad Lotia Anbang Wen Andreas Rammhold +Christophe Nowicki Daniel Kahn Gillmor Daniel Salzman daurnimator @@ -20,6 +21,8 @@ Grigorii Demidov Hasnat Ivana Krumlová +Jakub Ružička +Jan Hák Jan Holuša Jan Pavlinec Jan Včelák @@ -44,12 +47,14 @@ rickhg12hs Robert Šefr SH +Simon South Štěpán Balážik Štěpán Kotek The Gitter Badger Tomáš Hozza Tomáš Křížek Ulrich Wisser +Vašek Šraier Vicky Shrestha Vítězslav Kříž Vladimír Čunát diff -Nru knot-resolver-5.1.1/ci/debian-buster/Dockerfile knot-resolver-5.2.1/ci/debian-buster/Dockerfile --- knot-resolver-5.1.1/ci/debian-buster/Dockerfile 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/ci/debian-buster/Dockerfile 2020-12-09 09:44:29.000000000 +0000 @@ -2,7 +2,8 @@ FROM debian:buster MAINTAINER Knot Resolver -ARG KNOT_BRANCH=2.9 +# >= 3.0 needed because of --enable-xdp=yes +ARG KNOT_BRANCH=3.0 ENV DEBIAN_FRONTEND=noninteractive WORKDIR /root @@ -14,7 +15,7 @@ # RUN apt-get upgrade -y -qqq # Knot and Knot Resolver dependecies -RUN apt-get install -y -qqq git make cmake pkg-config build-essential bsdmainutils libtool autoconf liburcu-dev libgnutls28-dev libedit-dev liblmdb-dev libcap-ng-dev libsystemd-dev libidn11-dev protobuf-c-compiler libfstrm-dev libuv1-dev libcmocka-dev libluajit-5.1-dev lua-http meson libssl-dev libnghttp2-dev +RUN apt-get install -y -qqq git make cmake pkg-config build-essential bsdmainutils libtool autoconf liburcu-dev libgnutls28-dev libedit-dev liblmdb-dev libcap-ng-dev libsystemd-dev libidn11-dev protobuf-c-compiler libfstrm-dev libuv1-dev libcmocka-dev libluajit-5.1-dev lua-http meson libssl-dev libnghttp2-dev libelf-dev # documentation dependecies RUN apt-get install -y -qqq doxygen python3-sphinx python3-breathe python3-sphinx-rtd-theme @@ -26,11 +27,11 @@ RUN pip3 install pylint RUN pip3 install pep8 RUN pip3 install pytest-xdist -# tests/pytest dependencies -RUN pip3 install dnspython jinja2 pytest pytest-html pytest-xdist +# tests/pytest dependencies: skip over broken versions +RUN pip3 install 'dnspython != 2.0.0' jinja2 'pytest != 6.0.0' pytest-html pytest-xdist -# Wireshark/dumpcap for Deckard -RUN apt-get install -y -qqq wireshark-common +# packet capture tools for Deckard +RUN apt-get install --no-install-suggests --no-install-recommends -y -qqq tcpdump wireshark-common # Faketime for Deckard RUN apt-get install -y -qqq faketime @@ -38,15 +39,15 @@ # C dependencies for python-augeas RUN apt-get install -y -qqq libaugeas-dev libffi-dev # Python dependencies for Deckard -RUN wget https://gitlab.labs.nic.cz/knot/deckard/raw/master/requirements.txt -O /tmp/deckard-req.txt +RUN wget https://gitlab.nic.cz/knot/deckard/raw/master/requirements.txt -O /tmp/deckard-req.txt RUN pip3 install -r /tmp/deckard-req.txt # build and install latest version of Knot DNS -RUN git clone --depth=1 --branch=$KNOT_BRANCH https://gitlab.labs.nic.cz/knot/knot-dns.git /tmp/knot +RUN git clone --depth=1 --branch=$KNOT_BRANCH https://gitlab.nic.cz/knot/knot-dns.git /tmp/knot WORKDIR /tmp/knot RUN pwd RUN autoreconf -if -RUN ./configure --prefix=/usr +RUN ./configure --prefix=/usr --enable-xdp=yes RUN CFLAGS="-g" make RUN make install RUN ldconfig @@ -58,11 +59,11 @@ # Lua lint for kresd CI RUN apt-get install luarocks -y -qqq -RUN luarocks install luacheck +RUN luarocks --lua-version 5.1 install luacheck # respdiff for kresd CI RUN apt-get install lmdb-utils -y -qqq -RUN git clone --depth=1 https://gitlab.labs.nic.cz/knot/respdiff /var/opt/respdiff +RUN git clone --depth=1 https://gitlab.nic.cz/knot/respdiff /var/opt/respdiff RUN pip3 install -r /var/opt/respdiff/requirements.txt # Python static analysis for respdiff @@ -89,7 +90,7 @@ # code coverage RUN apt-get install -y -qqq lcov -RUN luarocks install luacov +RUN luarocks --lua-version 5.1 install luacov # LuaJIT binary for stand-alone scripting RUN apt-get install -y -qqq luajit diff -Nru knot-resolver-5.1.1/ci/deckard_commit_check.sh knot-resolver-5.2.1/ci/deckard_commit_check.sh --- knot-resolver-5.1.1/ci/deckard_commit_check.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/ci/deckard_commit_check.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,13 @@ +DECKARD_COMMIT=$(git ls-tree HEAD:tests/integration/ | grep commit | grep deckard | cut -f1 | cut -f3 '-d ') +DECKARD_PATH="tests/integration/deckard" +pushd $DECKARD_PATH > /dev/null +if git merge-base --is-ancestor $DECKARD_COMMIT origin/master; then + echo "Deckard submodule commit is on in its master branch. All good in the hood." + exit 0 +else + echo "Deckard submodule commit $DECKARD_COMMIT is not in Deckard's master branch." + echo "This WILL cause CI breakages so make sure your changes in Deckard are merged" + echo "or point the submodule to another commit." + exit 1 +fi + diff -Nru knot-resolver-5.1.1/ci/gh_actions.py knot-resolver-5.2.1/ci/gh_actions.py --- knot-resolver-5.1.1/ci/gh_actions.py 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/ci/gh_actions.py 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,53 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-3.0-or-later +import json +import time +import sys + +import requests + + +BRANCH_API_ENDPOINT = "https://api.github.com/repos/CZ-NIC/knot-resolver/actions/runs?branch={branch}" # noqa +TIMEOUT = 15*60 # 15 mins max +POLL_DELAY = 60 + + +def exit(msg='', html_url='', code=1): + print(msg, file=sys.stderr) + print(html_url) + sys.exit(code) + + +end_time = time.time() + TIMEOUT +while time.time() < end_time: + response = requests.get( + BRANCH_API_ENDPOINT.format(branch=sys.argv[1]), + headers={"Accept": "application/vnd.github.v3+json"}) + if response.status_code == 404: + pass # not created yet? + elif response.status_code == 200: + data = json.loads(response.content.decode('utf-8')) + try: + run = data['workflow_runs'][0] + conclusion = run['conclusion'] + html_url = run['html_url'] + commit_sha = run['head_sha'] + except (KeyError, IndexError): + time.sleep(POLL_DELAY) + continue + + if commit_sha != sys.argv[2]: + exit("Fetched invalid GH Action: commit mismatch. Re-run or push again?") + + if conclusion is None: + pass + if conclusion == "success": + exit("SUCCESS!", html_url, code=0) + elif isinstance(conclusion, str): + # failure, neutral, cancelled, skipped, timed_out, or action_required + exit("GitHub Actions Conclusion: {}!".format(conclusion.upper()), html_url) + else: + exit("API Response Code: {}".format(response.status_code), code=2) + time.sleep(POLL_DELAY) + +exit("Timed out!") diff -Nru knot-resolver-5.1.1/ci/README.md knot-resolver-5.2.1/ci/README.md --- knot-resolver-5.1.1/ci/README.md 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/ci/README.md 2020-12-09 09:44:29.000000000 +0000 @@ -4,23 +4,9 @@ * debian-buster ``` -$ export KNOT_BRANCH=2.9 -$ docker build --no-cache -t registry.labs.nic.cz/knot/knot-resolver/ci/debian-buster:knot-$KNOT_BRANCH --build-arg KNOT_BRANCH=$KNOT_BRANCH debian-buster +$ export KNOT_BRANCH=3.0 +$ docker build --no-cache -t registry.nic.cz/knot/knot-resolver/ci/debian-buster:knot-$KNOT_BRANCH --build-arg KNOT_BRANCH=$KNOT_BRANCH debian-buster -$ docker login registry.labs.nic.cz -$ docker push registry.labs.nic.cz/knot/knot-resolver/ci/debian-buster:knot-$KNOT_BRANCH -``` - -* turris - -``` -$ docker build --no-cache -t registry.labs.nic.cz/knot/knot-resolver/ci/turris:omnia turris -$ docker push registry.labs.nic.cz/knot/knot-resolver/ci/turris:omnia -``` - -Alternatively, provide `SDK_REPO` build arg (dir name from https://repo.turris.cz/ ) - -``` -$ docker build --no-cache --build-arg SDK_REPO=omnia-nightly -t registry.labs.nic.cz/knot/knot-resolver/ci/turris:omnia-nightly turris -$ docker push registry.labs.nic.cz/knot/knot-resolver/ci/turris:omnia-nightly +$ docker login registry.nic.cz +$ docker push registry.nic.cz/knot/knot-resolver/ci/debian-buster:knot-$KNOT_BRANCH ``` diff -Nru knot-resolver-5.1.1/ci/respdiff/kresd.config knot-resolver-5.2.1/ci/respdiff/kresd.config --- knot-resolver-5.1.1/ci/respdiff/kresd.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/ci/respdiff/kresd.config 2020-12-09 09:44:29.000000000 +0000 @@ -19,4 +19,8 @@ 'stats', -- Track internal statistics } +-- avoid TC flags returned to respdiff +local _, up_bs = net.bufsize() +net.bufsize(4096, up_bs) + verbose(true) diff -Nru knot-resolver-5.1.1/ci/respdiff/run-respdiff-tests.sh knot-resolver-5.2.1/ci/respdiff/run-respdiff-tests.sh --- knot-resolver-5.1.1/ci/respdiff/run-respdiff-tests.sh 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/ci/respdiff/run-respdiff-tests.sh 2020-12-09 09:44:29.000000000 +0000 @@ -7,7 +7,7 @@ NDIFFREPRO=3 -wget -qO- https://gitlab.labs.nic.cz/knot/respdiff/snippets/238/raw?inline=false | head -n 5000 > /tmp/queries.txt +wget -qO- https://gitlab.nic.cz/knot/respdiff/snippets/238/raw?inline=false | head -n 5000 > /tmp/queries.txt mkdir results rm -rf respdiff.db diff -Nru knot-resolver-5.1.1/ci/travis.py knot-resolver-5.2.1/ci/travis.py --- knot-resolver-5.1.1/ci/travis.py 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/ci/travis.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,50 +0,0 @@ -#!/usr/bin/python3 -# SPDX-License-Identifier: GPL-3.0-or-later -import json -import time -import sys - -import requests - - -BRANCH_API_ENDPOINT = "https://api.travis-ci.com/repos/CZ-NIC/knot-resolver/branches/{branch}" -JOB_URL = "https://travis-ci.com/CZ-NIC/knot-resolver/jobs/{job_id}" -TIMEOUT = 600 # 10 mins max -POLL_DELAY = 15 - -job_id = None - - -def exit(msg='', code=1): - print(msg, file=sys.stderr) - if job_id is not None: - print(JOB_URL.format(job_id=job_id)) - sys.exit(code) - - -end_time = time.time() + TIMEOUT -while time.time() < end_time: - response = requests.get( - BRANCH_API_ENDPOINT.format(branch=sys.argv[1]), - headers={"Accept": "application/vnd.travis-ci.2.1+json"}) - if response.status_code == 404: - pass # not created yet? - elif response.status_code == 200: - data = json.loads(response.content.decode('utf-8')) - state = data['branch']['state'] - try: - job_id = data['branch']['job_ids'][0] - except KeyError: - pass - - if state == "passed": - exit("Travis CI Result: PASSED!", code=0) - elif state == "created" or state == "started": - pass - else: - exit("Travis CI Result: {}!".format(state.upper())) - else: - exit("API Response Code: {}".format(response.status_code), code=2) - time.sleep(POLL_DELAY) - -exit("Timed out!") diff -Nru knot-resolver-5.1.1/ci/turris/Dockerfile knot-resolver-5.2.1/ci/turris/Dockerfile --- knot-resolver-5.1.1/ci/turris/Dockerfile 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/ci/turris/Dockerfile 1970-01-01 00:00:00.000000000 +0000 @@ -1,23 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later - -FROM debian:stable -MAINTAINER Knot Resolver -ARG SDK_NAME=OpenWrt-SDK*x86_64 -ARG SDK_REPO=omnia - -WORKDIR /tmp - -RUN echo "deb http://ftp.debian.org/debian stretch-backports main" >> /etc/apt/sources.list - -RUN apt-get update -qq -RUN apt-get -t stretch-backports install -y git -RUN apt-get install -y -qqq git-core build-essential libssl-dev libncurses5-dev \ - unzip gawk zlib1g-dev git subversion mercurial ccache libtinfo-dev libncurses5 \ - libncurses5-dev wget - -RUN wget --quiet "https://repo.turris.cz/$SDK_REPO/" -r -nd -np --accept="$SDK_NAME.tar.bz2" && \ - tar xjf $SDK_NAME.tar.bz2 && \ - rm $SDK_NAME.tar.bz2 && \ - mv $SDK_NAME turris - -CMD ["/bin/bash"] diff -Nru knot-resolver-5.1.1/client/packaging/debian/10/builddeps knot-resolver-5.2.1/client/packaging/debian/10/builddeps --- knot-resolver-5.1.1/client/packaging/debian/10/builddeps 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/client/packaging/debian/10/builddeps 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -libedit-dev diff -Nru knot-resolver-5.1.1/client/packaging/debian/10/rundeps knot-resolver-5.2.1/client/packaging/debian/10/rundeps --- knot-resolver-5.1.1/client/packaging/debian/10/rundeps 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/client/packaging/debian/10/rundeps 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -libedit2 diff -Nru knot-resolver-5.1.1/client/packaging/test.sh knot-resolver-5.2.1/client/packaging/test.sh --- knot-resolver-5.1.1/client/packaging/test.sh 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/client/packaging/test.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-3.0-or-later -test -e /usr/sbin/kresc -/usr/sbin/kresc # command will fail because of invalid parameters -test "$?" -eq 1 # linker error would have different exit code diff -Nru knot-resolver-5.1.1/client/.packaging/test.sh knot-resolver-5.2.1/client/.packaging/test.sh --- knot-resolver-5.1.1/client/.packaging/test.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/client/.packaging/test.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,5 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +test -e sbin/kresc +sbin/kresc # command will fail because of invalid parameters +test "$?" -eq 1 # linker error would have different exit code diff -Nru knot-resolver-5.1.1/contrib/base32hex.spdx knot-resolver-5.2.1/contrib/base32hex.spdx --- knot-resolver-5.1.1/contrib/base32hex.spdx 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/contrib/base32hex.spdx 2020-12-09 09:44:29.000000000 +0000 @@ -5,6 +5,6 @@ DocumentNamespace: http://spdx.org/spdxdocs/spdx-v2.1-4f29f08d-5fbf-4793-934c-9a6a2e6d5517 PackageName: knotdns-base32hex -PackageDownloadLocation: git+https://gitlab.labs.nic.cz/knot/knot-dns.git@2b3c828a4cb8d9595318552483d4947345426c30#src/libknot/internal/base32hex.c +PackageDownloadLocation: git+https://gitlab.nic.cz/knot/knot-dns.git@2b3c828a4cb8d9595318552483d4947345426c30#src/libknot/internal/base32hex.c PackageOriginator: Organization: Knot DNS contributors PackageLicenseDeclared: GPL-3.0-or-later diff -Nru knot-resolver-5.1.1/contrib/base64.c knot-resolver-5.2.1/contrib/base64.c --- knot-resolver-5.1.1/contrib/base64.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/contrib/base64.c 2020-12-09 09:44:29.000000000 +0000 @@ -1,4 +1,4 @@ -/* Copyright (C) 2011-2017 CZ.NIC, z.s.p.o. +/* Copyright (C) 2020 CZ.NIC, z.s.p.o. * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -142,6 +142,7 @@ int32_t ret = kr_base64_encode(in, in_len, *out, out_len); if (ret < 0) { free(*out); + *out = NULL; } return ret; @@ -203,8 +204,10 @@ switch (pad_len) { case 0: bin[2] = (c3 << 6) + c4; + // FALLTHROUGH case 1: bin[1] = (c2 << 4) + (c3 >> 2); + // FALLTHROUGH case 2: bin[0] = (c1 << 2) + (c2 >> 4); } @@ -250,6 +253,7 @@ int32_t ret = kr_base64_decode(in, in_len, *out, out_len); if (ret < 0) { free(*out); + *out = NULL; } return ret; diff -Nru knot-resolver-5.1.1/contrib/base64.spdx knot-resolver-5.2.1/contrib/base64.spdx --- knot-resolver-5.1.1/contrib/base64.spdx 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/contrib/base64.spdx 2020-12-09 09:44:29.000000000 +0000 @@ -5,6 +5,6 @@ DocumentNamespace: http://spdx.org/spdxdocs/spdx-v2.1-669dfa8c-3b50-425f-92fc-9b7ce18999f2 PackageName: knotdns-base64 -PackageDownloadLocation: git+https://gitlab.labs.nic.cz/knot/knot-dns.git@2b3c828a4cb8d9595318552483d4947345426c30#src/libknot/internal/base64.c +PackageDownloadLocation: git+https://gitlab.nic.cz/knot/knot-dns.git@2b3c828a4cb8d9595318552483d4947345426c30#src/libknot/internal/base64.c PackageOriginator: Organization: Knot DNS contributors PackageLicenseDeclared: GPL-3.0-or-later diff -Nru knot-resolver-5.1.1/contrib/base64url.c knot-resolver-5.2.1/contrib/base64url.c --- knot-resolver-5.1.1/contrib/base64url.c 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/contrib/base64url.c 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,287 @@ +/* Copyright (C) 2020 CZ.NIC, z.s.p.o. + + 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 3 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 "contrib/base64url.h" +#include "libknot/errcode.h" + +#include +#include +#include + +/*! \brief Maximal length of binary input to Base64url encoding. */ +#define MAX_BIN_DATA_LEN ((INT32_MAX / 4) * 3) + +/*! \brief Base64url padding character. */ +static const uint8_t base64url_pad = '\0'; +/*! \brief Base64 alphabet. */ +static const uint8_t base64url_enc[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +/*! \brief Indicates bad Base64 character. */ +#define KO 255 +/*! \brief Indicates Base64 padding character. */ +#define PD 64 + +/*! \brief Transformation and validation table for decoding Base64. */ +static const uint8_t base64url_dec[256] = { + [ 0] = PD, [ 43] = KO, ['V'] = 21, [129] = KO, [172] = KO, [215] = KO, + [ 1] = KO, [ 44] = KO, ['W'] = 22, [130] = KO, [173] = KO, [216] = KO, + [ 2] = KO, ['-'] = 62, ['X'] = 23, [131] = KO, [174] = KO, [217] = KO, + [ 3] = KO, [ 46] = KO, ['Y'] = 24, [132] = KO, [175] = KO, [218] = KO, + [ 4] = KO, [ 47] = KO, ['Z'] = 25, [133] = KO, [176] = KO, [219] = KO, + [ 5] = KO, ['0'] = 52, [ 91] = KO, [134] = KO, [177] = KO, [220] = KO, + [ 6] = KO, ['1'] = 53, [ 92] = KO, [135] = KO, [178] = KO, [221] = KO, + [ 7] = KO, ['2'] = 54, [ 93] = KO, [136] = KO, [179] = KO, [222] = KO, + [ 8] = KO, ['3'] = 55, [ 94] = KO, [137] = KO, [180] = KO, [223] = KO, + [ 9] = KO, ['4'] = 56, ['_'] = 63, [138] = KO, [181] = KO, [224] = KO, + [ 10] = KO, ['5'] = 57, [ 96] = KO, [139] = KO, [182] = KO, [225] = KO, + [ 11] = KO, ['6'] = 58, ['a'] = 26, [140] = KO, [183] = KO, [226] = KO, + [ 12] = KO, ['7'] = 59, ['b'] = 27, [141] = KO, [184] = KO, [227] = KO, + [ 13] = KO, ['8'] = 60, ['c'] = 28, [142] = KO, [185] = KO, [228] = KO, + [ 14] = KO, ['9'] = 61, ['d'] = 29, [143] = KO, [186] = KO, [229] = KO, + [ 15] = KO, [ 58] = KO, ['e'] = 30, [144] = KO, [187] = KO, [230] = KO, + [ 16] = KO, [ 59] = KO, ['f'] = 31, [145] = KO, [188] = KO, [231] = KO, + [ 17] = KO, [ 60] = KO, ['g'] = 32, [146] = KO, [189] = KO, [232] = KO, + [ 18] = KO, [ 61] = KO, ['h'] = 33, [147] = KO, [190] = KO, [233] = KO, + [ 19] = KO, [ 62] = KO, ['i'] = 34, [148] = KO, [191] = KO, [234] = KO, + [ 20] = KO, [ 63] = KO, ['j'] = 35, [149] = KO, [192] = KO, [235] = KO, + [ 21] = KO, [ 64] = KO, ['k'] = 36, [150] = KO, [193] = KO, [236] = KO, + [ 22] = KO, ['A'] = 0, ['l'] = 37, [151] = KO, [194] = KO, [237] = KO, + [ 23] = KO, ['B'] = 1, ['m'] = 38, [152] = KO, [195] = KO, [238] = KO, + [ 24] = KO, ['C'] = 2, ['n'] = 39, [153] = KO, [196] = KO, [239] = KO, + [ 25] = KO, ['D'] = 3, ['o'] = 40, [154] = KO, [197] = KO, [240] = KO, + [ 26] = KO, ['E'] = 4, ['p'] = 41, [155] = KO, [198] = KO, [241] = KO, + [ 27] = KO, ['F'] = 5, ['q'] = 42, [156] = KO, [199] = KO, [242] = KO, + [ 28] = KO, ['G'] = 6, ['r'] = 43, [157] = KO, [200] = KO, [243] = KO, + [ 29] = KO, ['H'] = 7, ['s'] = 44, [158] = KO, [201] = KO, [244] = KO, + [ 30] = KO, ['I'] = 8, ['t'] = 45, [159] = KO, [202] = KO, [245] = KO, + [ 31] = KO, ['J'] = 9, ['u'] = 46, [160] = KO, [203] = KO, [246] = KO, + [ 32] = KO, ['K'] = 10, ['v'] = 47, [161] = KO, [204] = KO, [247] = KO, + [ 33] = KO, ['L'] = 11, ['w'] = 48, [162] = KO, [205] = KO, [248] = KO, + [ 34] = KO, ['M'] = 12, ['x'] = 49, [163] = KO, [206] = KO, [249] = KO, + [ 35] = KO, ['N'] = 13, ['y'] = 50, [164] = KO, [207] = KO, [250] = KO, + [ 36] = KO, ['O'] = 14, ['z'] = 51, [165] = KO, [208] = KO, [251] = KO, + ['%'] = KO, ['P'] = 15, [123] = KO, [166] = KO, [209] = KO, [252] = KO, + [ 38] = KO, ['Q'] = 16, [124] = KO, [167] = KO, [210] = KO, [253] = KO, + [ 39] = KO, ['R'] = 17, [125] = KO, [168] = KO, [211] = KO, [254] = KO, + [ 40] = KO, ['S'] = 18, [126] = KO, [169] = KO, [212] = KO, [255] = KO, + [ 41] = KO, ['T'] = 19, [127] = KO, [170] = KO, [213] = KO, + [ 42] = KO, ['U'] = 20, [128] = KO, [171] = KO, [214] = KO, +}; + +int32_t kr_base64url_encode(const uint8_t *in, + const uint32_t in_len, + uint8_t *out, + const uint32_t out_len) +{ + // Checking inputs. + if (in == NULL || out == NULL) { + return KNOT_EINVAL; + } + if (in_len > MAX_BIN_DATA_LEN || out_len < ((in_len + 2) / 3) * 4) { + return KNOT_ERANGE; + } + + uint8_t rest_len = in_len % 3; + const uint8_t *stop = in + in_len - rest_len; + uint8_t *text = out; + + // Encoding loop takes 3 bytes and creates 4 characters. + while (in < stop) { + text[0] = base64url_enc[in[0] >> 2]; + text[1] = base64url_enc[(in[0] & 0x03) << 4 | in[1] >> 4]; + text[2] = base64url_enc[(in[1] & 0x0F) << 2 | in[2] >> 6]; + text[3] = base64url_enc[in[2] & 0x3F]; + text += 4; + in += 3; + } + + // Processing of padding, if any. + switch (rest_len) { + case 2: + text[0] = base64url_enc[in[0] >> 2]; + text[1] = base64url_enc[(in[0] & 0x03) << 4 | in[1] >> 4]; + text[2] = base64url_enc[(in[1] & 0x0F) << 2]; + text[3] = base64url_pad; + text += 3; + break; + case 1: + text[0] = base64url_enc[in[0] >> 2]; + text[1] = base64url_enc[(in[0] & 0x03) << 4]; + text[2] = base64url_pad; + text[3] = base64url_pad; + text += 2; + break; + } + return (text - out); +} + +int32_t kr_base64url_encode_alloc(const uint8_t *in, + const uint32_t in_len, + uint8_t **out) +{ + // Checking inputs. + if (out == NULL) { + return KNOT_EINVAL; + } + if (in_len > MAX_BIN_DATA_LEN) { + return KNOT_ERANGE; + } + + // Compute output buffer length. + uint32_t out_len = ((in_len + 2) / 3) * 4; + + // Allocate output buffer. + *out = malloc(out_len); + if (*out == NULL) { + return KNOT_ENOMEM; + } + + // Encode data. + int32_t ret = kr_base64url_encode(in, in_len, *out, out_len); + if (ret < 0) { + free(*out); + *out = NULL; + } + + return ret; +} + +int32_t kr_base64url_decode(const uint8_t *in, + uint32_t in_len, + uint8_t *out, + const uint32_t out_len) +{ + // Checking inputs. + if (in == NULL || out == NULL) { + return KNOT_EINVAL; + } + + // cut up to two "%3d" from the end of input + int pad3d = 0; + const uint8_t *end = in + in_len; + char *perc3d = "d3%d3%", *stop3d = perc3d + 6; + while (end != in && perc3d != stop3d && tolower(*--end) == *perc3d) { + if (*perc3d++ == '%') { + in_len -= 3; + pad3d++; + } + } + + if (in_len > INT32_MAX || out_len < ((in_len + 3) / 4) * 3) { + return KNOT_ERANGE; + } + + const uint8_t *stop = in + in_len; + uint8_t *bin = out; + uint8_t pad_len = 0; + uint8_t c1, c2, c3, c4; + + // Decoding loop takes 4 characters and creates 3 bytes. + while (in < stop) { + // Filling and transforming 4 Base64 chars. + c1 = base64url_dec[in[0]] ; + c2 = base64url_dec[in[1]] ; + c3 = (in + 2 < stop) ? base64url_dec[in[2]] : PD; + c4 = (in + 3 < stop) ? base64url_dec[in[3]] : PD; + + // Check 1. and 2. chars if are not padding + if (c1 >= PD || c2 >= PD) { + return KNOT_BASE64_ECHAR; + } + // Check 3. char if is bad or padding. + else if (c3 >= PD) { + if (c3 == PD) { + pad_len = 2; + } else { + return KNOT_BASE64_ECHAR; + } + } + // Check 3. char if is bad or padding. + else if (c4 >= PD) { + if (c4 == PD) { + pad_len = 1; + } else { + return KNOT_BASE64_ECHAR; + } + } + + if (pad_len > 0 && in <= stop - 4) { + return KNOT_BASE64_ECHAR; + } + + // Computing of output data based on padding length. + switch (pad_len) { + case 0: + bin[2] = (c3 << 6) + c4; + // FALLTHROUGH + case 1: + bin[1] = (c2 << 4) + (c3 >> 2); + // FALLTHROUGH + case 2: + bin[0] = (c1 << 2) + (c2 >> 4); + } + + // Update output end. + switch (pad_len) { + case 0: + bin += 3; + break; + case 1: + bin += 2; + goto end; + case 2: + bin += 1; + goto end; + } + + in += 4; + } + +end: + if (pad3d > pad_len) { + return KNOT_BASE64_ECHAR; + } + return (bin - out); +} + +int32_t kr_base64url_decode_alloc(const uint8_t *in, + const uint32_t in_len, + uint8_t **out) +{ + // Checking inputs. + if (out == NULL) { + return KNOT_EINVAL; + } + + // Compute output buffer length. + uint32_t out_len = ((in_len + 3) / 4) * 3; + + // Allocate output buffer. + *out = malloc(out_len); + if (*out == NULL) { + return KNOT_ENOMEM; + } + + // Decode data. + int32_t ret = kr_base64url_decode(in, in_len, *out, out_len); + if (ret < 0) { + free(*out); + *out = NULL; + } + + return ret; +} diff -Nru knot-resolver-5.1.1/contrib/base64url.h knot-resolver-5.2.1/contrib/base64url.h --- knot-resolver-5.1.1/contrib/base64url.h 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/contrib/base64url.h 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,103 @@ +/* Copyright (C) 2020 CZ.NIC, z.s.p.o. + + 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 3 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 . + */ + +/*! + * \brief Base64url implementation (RFC 4648). + */ + +#pragma once + +#include + +/*! + * \brief Encodes binary data using Base64. + * + * \note Output data buffer contains Base64 text string which isn't + * terminated with '\0'! + * + * \param in Input binary data. + * \param in_len Length of input data. + * \param out Output data buffer. + * \param out_len Size of output buffer. + * + * \retval >=0 length of output string. + * \retval KNOT_E* if error. + */ +int32_t kr_base64url_encode(const uint8_t *in, + const uint32_t in_len, + uint8_t *out, + const uint32_t out_len); + +/*! + * \brief Encodes binary data using Base64 and output stores to own buffer. + * + * \note Output data buffer contains Base64 text string which isn't + * terminated with '\0'! + * + * \note Output buffer should be deallocated after use. + * + * \param in Input binary data. + * \param in_len Length of input data. + * \param out Output data buffer. + * + * \retval >=0 length of output string. + * \retval KNOT_E* if error. + */ +int32_t kr_base64url_encode_alloc(const uint8_t *in, + const uint32_t in_len, + uint8_t **out); + +/*! + * \brief Decodes text data using Base64. + * + * \note Input data needn't be terminated with '\0'. + * + * \note Input data must be continuous Base64 string! + * + * \param in Input text data. + * \param in_len Length of input string. + * \param out Output data buffer. + * \param out_len Size of output buffer. + * + * \retval >=0 length of output data. + * \retval KNOT_E* if error. + */ +int32_t kr_base64url_decode(const uint8_t *in, + uint32_t in_len, + uint8_t *out, + const uint32_t out_len); + +/*! + * \brief Decodes text data using Base64 and output stores to own buffer. + * + * \note Input data needn't be terminated with '\0'. + * + * \note Input data must be continuous Base64 string! + * + * \note Output buffer should be deallocated after use. + * + * \param in Input text data. + * \param in_len Length of input string. + * \param out Output data buffer. + * + * \retval >=0 length of output data. + * \retval KNOT_E* if error. + */ +int32_t kr_base64url_decode_alloc(const uint8_t *in, + const uint32_t in_len, + uint8_t **out); + +/*! @} */ diff -Nru knot-resolver-5.1.1/contrib/cleanup.h knot-resolver-5.2.1/contrib/cleanup.h --- knot-resolver-5.1.1/contrib/cleanup.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/contrib/cleanup.h 2020-12-09 09:44:29.000000000 +0000 @@ -11,12 +11,12 @@ #include #define auto_free __attribute__((cleanup(_cleanup_free))) -static inline void _cleanup_free(char **p) { - free(*p); +static inline void _cleanup_free(const void *p) { + free(*(char **)p); } #define auto_close __attribute__((cleanup(_cleanup_close))) static inline void _cleanup_close(int *p) { - if (*p > 0) close(*p); + if (*p != -1) close(*p); } #define auto_fclose __attribute__((cleanup(_cleanup_fclose))) static inline void _cleanup_fclose(FILE **p) { diff -Nru knot-resolver-5.1.1/contrib/dynarray.spdx knot-resolver-5.2.1/contrib/dynarray.spdx --- knot-resolver-5.1.1/contrib/dynarray.spdx 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/contrib/dynarray.spdx 2020-12-09 09:44:29.000000000 +0000 @@ -5,6 +5,6 @@ DocumentNamespace: http://spdx.org/spdxdocs/spdx-v2.1-ce6423dd-ac6a-4e78-90c3-5cbdef1e252c PackageName: knotdns-dynarray -PackageDownloadLocation: git+https://gitlab.labs.nic.cz/knot/knot-dns.git@48c8b4f38cf5f7bf505c79b56adf7580688f6d3d#src/contrib/dynarray.h +PackageDownloadLocation: git+https://gitlab.nic.cz/knot/knot-dns.git@48c8b4f38cf5f7bf505c79b56adf7580688f6d3d#src/contrib/dynarray.h PackageOriginator: Organization: Knot DNS contributors PackageLicenseDeclared: GPL-3.0-or-later diff -Nru knot-resolver-5.1.1/contrib/meson.build knot-resolver-5.2.1/contrib/meson.build --- knot-resolver-5.1.1/contrib/meson.build 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/contrib/meson.build 2020-12-09 09:44:29.000000000 +0000 @@ -8,7 +8,8 @@ 'ucw/mempool-fmt.c', 'murmurhash3/murmurhash3.c', 'base32hex.c', - 'base64.c' + 'base64.c', + 'base64url.c' ]) contrib_inc = include_directories('.', '..') diff -Nru knot-resolver-5.1.1/contrib/wire.spdx knot-resolver-5.2.1/contrib/wire.spdx --- knot-resolver-5.1.1/contrib/wire.spdx 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/contrib/wire.spdx 2020-12-09 09:44:29.000000000 +0000 @@ -5,6 +5,6 @@ DocumentNamespace: http://spdx.org/spdxdocs/spdx-v2.1-0f7c0ad6-ea88-44b4-a93a-850f3b6ccade PackageName: knotdns-wire -PackageDownloadLocation: git+https://gitlab.labs.nic.cz/knot/knot-dns.git@824ce5e81bc1a1d333de3042b2745bb2387dc2ff#src/contrib/wire.h +PackageDownloadLocation: git+https://gitlab.nic.cz/knot/knot-dns.git@824ce5e81bc1a1d333de3042b2745bb2387dc2ff#src/contrib/wire.h PackageOriginator: Organization: Knot DNS contributors PackageLicenseDeclared: GPL-3.0-or-later diff -Nru knot-resolver-5.1.1/CONTRIBUTING.md knot-resolver-5.2.1/CONTRIBUTING.md --- knot-resolver-5.1.1/CONTRIBUTING.md 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/CONTRIBUTING.md 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,19 @@ +Contributing +============ + +Please file issues and merge requests against the upstream repository: + +[https://gitlab.nic.cz/knot/knot-resolver](https://gitlab.nic.cz/knot/knot-resolver) + +Opening a merge request on gitlab.nic.cz +---------------------------------------- + +Unfortunately, due to administrative policy, forking is disabled by default. To +be able to fork, please send us an e-mail with your username to knot-resolver@labs.nic.cz + +We apologize for the inconvenience and if you can't be bothered, please +consider alternate ways of contributing, such as: + +- Opening a pull request on [github.com](https://github.com/CZ-NIC/knot-resolver). + We'll take care of it and move it to our upstream. +- Sending a patch to the users list: knot-resolver-users@lists.nic.cz diff -Nru knot-resolver-5.1.1/daemon/bindings/cache.c knot-resolver-5.2.1/daemon/bindings/cache.c --- knot-resolver-5.1.1/daemon/bindings/cache.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/bindings/cache.c 2020-12-09 09:44:29.000000000 +0000 @@ -4,14 +4,12 @@ #include "daemon/bindings/impl.h" -#include "daemon/worker.h" #include "daemon/zimport.h" /** @internal return cache, or throw lua error if not open */ -struct kr_cache * cache_assert_open(lua_State *L) +static struct kr_cache * cache_assert_open(lua_State *L) { - struct engine *engine = engine_luaget(L); - struct kr_cache *cache = &engine->resolver.cache; + struct kr_cache *cache = &the_worker->engine->resolver.cache; assert(cache); if (!cache || !kr_cache_is_open(cache)) lua_error_p(L, "no cache is open yet, use cache.open() or cache.size, etc."); @@ -21,7 +19,7 @@ /** Return available cached backends. */ static int cache_backends(lua_State *L) { - struct engine *engine = engine_luaget(L); + struct engine *engine = the_worker->engine; lua_newtable(L); for (unsigned i = 0; i < engine->backends.len; ++i) { @@ -82,6 +80,8 @@ add_stat(open); add_stat(close); add_stat(count); + cache->stats.count_entries = cache->api->count(cache->db, &cache->stats); + add_stat(count_entries); add_stat(clear); add_stat(commit); add_stat(read); @@ -93,6 +93,10 @@ add_stat(match_miss); add_stat(read_leq); add_stat(read_leq_miss); + /* usage_percent statistics special case - double */ + cache->stats.usage_percent = cache->api->usage_percent(cache->db); + lua_pushnumber(L, cache->stats.usage_percent); + lua_setfield(L, -2, "usage_percent"); #undef add_stat return 1; @@ -169,7 +173,7 @@ lua_error_p(L, "expected 'open(number max_size, string config = \"\")'"); /* Select cache storage backend */ - struct engine *engine = engine_luaget(L); + struct engine *engine = the_worker->engine; lua_Integer csize_lua = lua_tointeger(L, 1); if (!(csize_lua >= 8192 && csize_lua < SIZE_MAX)) { /* min. is basically arbitrary */ @@ -199,6 +203,13 @@ return luaL_error(L, "can't open cache path '%s'; working directory '%s'; %s", opts.path, cwd, kr_strerror(ret)); } + /* Let's check_health() every five seconds to avoid keeping old cache alive + * even in case of not having any work to do. */ + ret = kr_cache_check_health(&engine->resolver.cache, 5000); + if (ret != 0) { + kr_log_error("[cache] periodic health check failed (ignored): %s\n", + kr_strerror(ret)); + } /* Store current configuration */ lua_getglobal(L, "cache"); @@ -216,8 +227,7 @@ static int cache_close(lua_State *L) { - struct engine *engine = engine_luaget(L); - struct kr_cache *cache = &engine->resolver.cache; + struct kr_cache *cache = &the_worker->engine->resolver.cache; if (!kr_cache_is_open(cache)) { return 0; } @@ -257,10 +267,10 @@ lua_error_maybe(L, ret); /* Clear reputation tables */ - struct engine *engine = engine_luaget(L); - lru_reset(engine->resolver.cache_rtt); - lru_reset(engine->resolver.cache_rep); - lru_reset(engine->resolver.cache_cookie); + struct kr_context *ctx = &the_worker->engine->resolver; + lru_reset(ctx->cache_rtt); + lru_reset(ctx->cache_rep); + lru_reset(ctx->cache_cookie); lua_pushboolean(L, true); return 1; } @@ -332,8 +342,7 @@ * in NS elections again. */ static int cache_ns_tout(lua_State *L) { - struct engine *engine = engine_luaget(L); - struct kr_context *ctx = &engine->resolver; + struct kr_context *ctx = &the_worker->engine->resolver; /* Check parameters */ int n = lua_gettop(L); diff -Nru knot-resolver-5.1.1/daemon/bindings/cache.rst knot-resolver-5.2.1/daemon/bindings/cache.rst --- knot-resolver-5.1.1/daemon/bindings/cache.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/bindings/cache.rst 2020-12-09 09:44:29.000000000 +0000 @@ -186,7 +186,7 @@ .. function:: cache.stats() - Return table with low-level statistics for each internal cache operation. + Return table with low-level statistics for internal cache operation and storage. This counts each access to cache and does not directly map to individual DNS queries or resource records. For query-level statistics see :ref:`stats module `. @@ -196,23 +196,27 @@ .. code-block:: lua > cache.stats() - [read_leq_miss] => 4 - [write] => 189 - [read_leq] => 9 - [read] => 4313 - [read_miss] => 1143 - [open] => 0 + [clear] => 0 [close] => 0 - [remove_miss] => 0 [commit] => 117 - [match_miss] => 2 - [match] => 21 [count] => 2 - [clear] => 0 + [count_entries] => 6187 + [match] => 21 + [match_miss] => 2 + [open] => 0 + [read] => 4313 + [read_leq] => 9 + [read_leq_miss] => 4 + [read_miss] => 1143 [remove] => 17 + [remove_miss] => 0 + [usage_percent] => 15.625 + [write] => 189 + Cache operation `read_leq` (*read less or equal*, i.e. range search) was requested 9 times, and 4 out of 9 operations were finished with *cache miss*. + Cache contains 6187 internal entries which occupy 15.625 % cache size. .. function:: cache.max_ttl([ttl]) diff -Nru knot-resolver-5.1.1/daemon/bindings/event.c knot-resolver-5.2.1/daemon/bindings/event.c --- knot-resolver-5.1.1/daemon/bindings/event.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/bindings/event.c 2020-12-09 09:44:29.000000000 +0000 @@ -4,15 +4,12 @@ #include "daemon/bindings/impl.h" -#include "daemon/worker.h" - #include #include static void event_free(uv_timer_t *timer) { - struct worker_ctx *worker = timer->loop->data; - lua_State *L = worker->engine->L; + lua_State *L = the_worker->engine->L; int ref = (intptr_t) timer->data; luaL_unref(L, LUA_REGISTRYINDEX, ref); free(timer); @@ -20,8 +17,7 @@ static void event_callback(uv_timer_t *timer) { - struct worker_ctx *worker = timer->loop->data; - lua_State *L = worker->engine->L; + lua_State *L = the_worker->engine->L; /* Retrieve callback and execute */ lua_rawgeti(L, LUA_REGISTRYINDEX, (intptr_t) timer->data); @@ -38,8 +34,7 @@ static void event_fdcallback(uv_poll_t* handle, int status, int events) { - struct worker_ctx *worker = handle->loop->data; - lua_State *L = worker->engine->L; + lua_State *L = the_worker->engine->L; /* Retrieve callback and execute */ lua_rawgeti(L, LUA_REGISTRYINDEX, (intptr_t) handle->data); @@ -99,7 +94,8 @@ { /* Check parameters */ int n = lua_gettop(L); - if (n < 2 || !lua_isnumber(L, 1) || !lua_isfunction(L, 2)) + if (n < 2 || !lua_isnumber(L, 1) || lua_tointeger(L, 1) == 0 + || !lua_isfunction(L, 2)) lua_error_p(L, "expected 'recurrent(number interval, function)'"); return event_sched(L, 0, lua_tointeger(L, 1)); diff -Nru knot-resolver-5.1.1/daemon/bindings/event.rst knot-resolver-5.2.1/daemon/bindings/event.rst --- knot-resolver-5.1.1/daemon/bindings/event.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/bindings/event.rst 2020-12-09 09:44:29.000000000 +0000 @@ -25,7 +25,7 @@ :return: event id - Similar to :func:`event.after()`, periodically execute function after ``interval`` passes. + Execute function immediatelly and then periodically after each ``interval``. Example: diff -Nru knot-resolver-5.1.1/daemon/bindings/impl.c knot-resolver-5.2.1/daemon/bindings/impl.c --- knot-resolver-5.1.1/daemon/bindings/impl.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/bindings/impl.c 2020-12-09 09:44:29.000000000 +0000 @@ -2,6 +2,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +#include #include #include #include @@ -29,6 +30,29 @@ return NULL; } +/** Return table listing filenames in a given directory (ls -A). */ +static int kluautil_list_dir(lua_State *L) +{ + lua_newtable(L); // empty table even on errors + + const char *path = lua_tolstring(L, 1, NULL); + if (!path) return 1; + DIR *dir = opendir(path); + if (!dir) return 1; + + struct dirent *entry; + int lua_i = 1; + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")) { + lua_pushstring(L, entry->d_name); + lua_rawseti(L, -2, lua_i++); + } + } + + closedir(dir); + return 1; +} + /* Each of these just creates the correspondingly named lua table of functions. */ int kr_bindings_cache (lua_State *L); /* ./cache.c */ @@ -44,12 +68,15 @@ kr_bindings_modules(L); kr_bindings_net(L); kr_bindings_worker(L); + + /* Finally some lua utils *written in C*, not really a binding. */ + lua_register(L, "kluautil_list_dir", kluautil_list_dir); } void lua_error_p(lua_State *L, const char *fmt, ...) { /* Add a stack trace and throw the result as a lua error. */ - luaL_traceback(L, L, "error occured here (config filename:lineno is at the bottom, if config is involved):", 0); + luaL_traceback(L, L, "error occurred here (config filename:lineno is at the bottom, if config is involved):", 0); /* Push formatted custom message, prepended with "ERROR: ". */ lua_pushliteral(L, "\nERROR: "); { diff -Nru knot-resolver-5.1.1/daemon/bindings/impl.h knot-resolver-5.2.1/daemon/bindings/impl.h --- knot-resolver-5.1.1/daemon/bindings/impl.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/bindings/impl.h 2020-12-09 09:44:29.000000000 +0000 @@ -5,6 +5,7 @@ #pragma once #include "daemon/engine.h" +#include "daemon/worker.h" /* the_worker is often useful */ #include #include @@ -15,7 +16,7 @@ #endif -/** Useful to stringify #defines into error strings. */ +/** Useful to stringify macros into error strings. */ #define STR(s) STRINGIFY_TOKEN(s) #define STRINGIFY_TOKEN(s) #s diff -Nru knot-resolver-5.1.1/daemon/bindings/modules.c knot-resolver-5.2.1/daemon/bindings/modules.c --- knot-resolver-5.1.1/daemon/bindings/modules.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/bindings/modules.c 2020-12-09 09:44:29.000000000 +0000 @@ -8,10 +8,10 @@ /** List loaded modules */ static int mod_list(lua_State *L) { - struct engine *engine = engine_luaget(L); + const module_array_t * const modules = &the_worker->engine->modules; lua_newtable(L); - for (unsigned i = 0; i < engine->modules.len; ++i) { - struct kr_module *module = engine->modules.at[i]; + for (unsigned i = 0; i < modules->len; ++i) { + struct kr_module *module = modules->at[i]; lua_pushstring(L, module->name); lua_rawseti(L, -2, i + 1); } @@ -33,8 +33,7 @@ const char *precedence = strtok(NULL, " "); const char *ref = strtok(NULL, " "); /* Load engine module */ - struct engine *engine = engine_luaget(L); - int ret = engine_register(engine, name, precedence, ref); + int ret = engine_register(the_worker->engine, name, precedence, ref); free(declaration); if (ret != 0) { if (ret == kr_error(EIDRM)) { @@ -56,8 +55,7 @@ if (n != 1 || !lua_isstring(L, 1)) lua_error_p(L, "expected 'unload(string name)'"); /* Unload engine module */ - struct engine *engine = engine_luaget(L); - int ret = engine_unregister(engine, lua_tostring(L, 1)); + int ret = engine_unregister(the_worker->engine, lua_tostring(L, 1)); lua_error_maybe(L, ret); lua_pushboolean(L, 1); diff -Nru knot-resolver-5.1.1/daemon/bindings/net.c knot-resolver-5.2.1/daemon/bindings/net.c --- knot-resolver-5.1.1/daemon/bindings/net.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/bindings/net.c 2020-12-09 09:44:29.000000000 +0000 @@ -1,4 +1,4 @@ -/* Copyright (C) 2015-2019 CZ.NIC, z.s.p.o. +/* Copyright (C) 2015-2020 CZ.NIC, z.s.p.o. * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -7,7 +7,6 @@ #include "contrib/base64.h" #include "daemon/network.h" #include "daemon/tls.h" -#include "daemon/worker.h" #include @@ -23,8 +22,12 @@ if (ep->flags.kind) { lua_pushstring(L, ep->flags.kind); + } else if (ep->flags.http && ep->flags.tls) { + lua_pushliteral(L, "doh2"); } else if (ep->flags.tls) { lua_pushliteral(L, "tls"); + } else if (ep->flags.xdp) { + lua_pushliteral(L, "xdp"); } else { lua_pushliteral(L, "dns"); } @@ -39,6 +42,9 @@ case AF_INET6: lua_pushliteral(L, "inet6"); break; + case AF_XDP: + lua_pushliteral(L, "inet4+inet6"); // both UDP ports at once + break; case AF_UNIX: lua_pushliteral(L, "unix"); break; @@ -49,17 +55,21 @@ lua_setfield(L, -2, "family"); lua_pushstring(L, key); - if (ep->family != AF_UNIX) { + if (ep->family == AF_INET || ep->family == AF_INET6) { lua_setfield(L, -2, "ip"); - } else { + lua_pushboolean(L, ep->flags.freebind); + lua_setfield(L, -2, "freebind"); + } else if (ep->family == AF_UNIX) { lua_setfield(L, -2, "path"); + } else if (ep->family == AF_XDP) { + lua_setfield(L, -2, "interface"); + lua_pushinteger(L, ep->nic_queue); + lua_setfield(L, -2, "nic_queue"); } if (ep->family != AF_UNIX) { lua_pushinteger(L, ep->port); lua_setfield(L, -2, "port"); - lua_pushboolean(L, ep->flags.freebind); - lua_setfield(L, -2, "freebind"); } if (ep->family == AF_UNIX) { @@ -86,19 +96,20 @@ /** List active endpoints. */ static int net_list(lua_State *L) { - struct engine *engine = engine_luaget(L); lua_newtable(L); lua_pushinteger(L, 1); - map_walk(&engine->net.endpoints, net_list_add, L); + map_walk(&the_worker->engine->net.endpoints, net_list_add, L); lua_pop(L, 1); return 1; } /** Listen on an address list represented by the top of lua stack. - * \note kind ownership is not transferred + * \note flags.kind ownership is not transferred, and flags.sock_type doesn't make sense * \return success */ -static bool net_listen_addrs(lua_State *L, int port, bool tls, const char *kind, bool freebind) +static bool net_listen_addrs(lua_State *L, int port, endpoint_flags_t flags, int16_t nic_queue) { + assert(flags.xdp || nic_queue == -1); + /* Case: table with 'addr' field; only follow that field directly. */ lua_getfield(L, -1, "addr"); if (!lua_isnil(L, -1)) { @@ -110,33 +121,56 @@ /* Case: string, representing a single address. */ const char *str = lua_tostring(L, -1); if (str != NULL) { - struct engine *engine = engine_luaget(L); + struct network *net = &the_worker->engine->net; + const bool is_unix = str[0] == '/'; int ret = 0; - endpoint_flags_t flags = { .tls = tls, .freebind = freebind }; - if (!kind && !flags.tls) { /* normal UDP */ + if (!flags.kind && !flags.tls) { /* normal UDP or XDP */ flags.sock_type = SOCK_DGRAM; - ret = network_listen(&engine->net, str, port, flags); + ret = network_listen(net, str, port, nic_queue, flags); } - if (!kind && ret == 0) { /* common for normal TCP and TLS */ + if (!flags.kind && !flags.xdp && ret == 0) { /* common for TCP, DoT and DoH (v2) */ flags.sock_type = SOCK_STREAM; - ret = network_listen(&engine->net, str, port, flags); + ret = network_listen(net, str, port, nic_queue, flags); } - if (kind) { - flags.kind = strdup(kind); + if (flags.kind) { + flags.kind = strdup(flags.kind); flags.sock_type = SOCK_STREAM; /* TODO: allow to override this? */ - ret = network_listen(&engine->net, str, port, flags); + ret = network_listen(net, str, (is_unix ? 0 : port), nic_queue, flags); } - if (ret != 0) { - if (str[0] == '/') { - kr_log_error("[system] bind to '%s' (UNIX): %s\n", - str, kr_strerror(ret)); + if (ret == 0) return true; /* success */ + + if (is_unix) { + kr_log_error("[system] bind to '%s' (UNIX): %s\n", + str, kr_strerror(ret)); + } else if (flags.xdp) { + const char *err_str = knot_strerror(ret); + if (ret == KNOT_ELIMIT) { + if ((strcmp(str, "::") == 0 || strcmp(str, "0.0.0.0") == 0)) { + err_str = "wildcard addresses not supported with XDP"; + } else { + err_str = "address matched multiple network interfaces"; + } + } else if (ret == kr_error(ENODEV)) { + err_str = "invalid address or interface name"; + } + /* Notable OK strerror: KNOT_EPERM Operation not permitted */ + + if (nic_queue == -1) { + kr_log_error("[system] failed to initialize XDP for '%s@%d'" + " (nic_queue = ): %s\n", + str, port, err_str); } else { - const char *stype = flags.sock_type == SOCK_DGRAM ? "UDP" : "TCP"; - kr_log_error("[system] bind to '%s@%d' (%s): %s\n", - str, port, stype, kr_strerror(ret)); + kr_log_error("[system] failed to initialize XDP for '%s@%d'" + " (nic_queue = %d): %s\n", + str, port, nic_queue, err_str); } + + } else { + const char *stype = flags.sock_type == SOCK_DGRAM ? "UDP" : "TCP"; + kr_log_error("[system] bind to '%s@%d' (%s): %s\n", + str, port, stype, kr_strerror(ret)); } - return ret == 0; + return false; /* failure */ } /* Last case: table where all entries are added recursively. */ @@ -144,7 +178,7 @@ lua_error_p(L, "bad type for address"); lua_pushnil(L); while (lua_next(L, -2)) { - if (!net_listen_addrs(L, port, tls, kind, freebind)) + if (!net_listen_addrs(L, port, flags, nic_queue)) return false; lua_pop(L, 1); } @@ -183,40 +217,64 @@ } } - bool tls = (port == KR_DNS_TLS_PORT); - bool freebind = false; - const char *kind = NULL; + endpoint_flags_t flags = { 0 }; + if (port == KR_DNS_TLS_PORT) { + flags.tls = true; + } else if (port == KR_DNS_DOH_PORT) { + flags.http = flags.tls = true; + } + + int16_t nic_queue = -1; if (n > 2 && !lua_isnil(L, 3)) { if (!lua_istable(L, 3)) lua_error_p(L, "wrong type of third parameter (table expected)"); - tls = table_get_flag(L, 3, "tls", tls); - freebind = table_get_flag(L, 3, "freebind", tls); + flags.tls = table_get_flag(L, 3, "tls", flags.tls); + flags.freebind = table_get_flag(L, 3, "freebind", false); lua_getfield(L, 3, "kind"); const char *k = lua_tostring(L, -1); if (k && strcasecmp(k, "dns") == 0) { - tls = false; - } else - if (k && strcasecmp(k, "tls") == 0) { - tls = true; - } else - if (k) { - kind = k; + flags.tls = flags.http = false; + } else if (k && strcasecmp(k, "xdp") == 0) { + flags.tls = flags.http = false; + flags.xdp = true; + } else if (k && strcasecmp(k, "tls") == 0) { + flags.tls = true; + flags.http = false; + } else if (k && strcasecmp(k, "doh2") == 0) { + flags.tls = flags.http = true; + } else if (k) { + flags.kind = k; + if (strcasecmp(k, "doh") == 0) { + kr_log_deprecate( + "kind=\"doh\" is an obsolete DoH implementation, use kind=\"doh2\" instead\n"); + } + } + + lua_getfield(L, 3, "nic_queue"); + if (lua_isnumber(L, -1)) { + if (flags.xdp) { + nic_queue = lua_tointeger(L, -1); + } else { + lua_error_p(L, "nic_queue only supported with kind = 'xdp'"); + } + } else if (!lua_isnil(L, -1)) { + lua_error_p(L, "wrong value of nic_queue (integer expected)"); } } /* Memory management of `kind` string is difficult due to longjmp etc. * Pop will unreference the lua value, so we store it on C stack instead (!) */ - const int kind_alen = kind ? strlen(kind) + 1 : 1 /* 0 length isn't C standard */; + const int kind_alen = flags.kind ? strlen(flags.kind) + 1 : 1 /* 0 length isn't C standard */; char kind_buf[kind_alen]; - if (kind) { - memcpy(kind_buf, kind, kind_alen); - kind = kind_buf; + if (flags.kind) { + memcpy(kind_buf, flags.kind, kind_alen); + flags.kind = kind_buf; } /* Now focus on the first argument. */ lua_settop(L, 1); - if (!net_listen_addrs(L, port, tls, kind, freebind)) + if (!net_listen_addrs(L, port, flags, nic_queue)) lua_error_p(L, "net.listen() failed to bind"); lua_pushboolean(L, true); return 1; @@ -240,8 +298,7 @@ if (!ok) lua_error_p(L, "expected 'close(string addr, [number port])'"); - struct network *net = &engine_luaget(L)->net; - int ret = network_close(net, addr, port); + int ret = network_close(&the_worker->engine->net, addr, port); lua_pushboolean(L, ret == 0); return 1; } @@ -301,16 +358,30 @@ /** Set UDP maximum payload size. */ static int net_bufsize(lua_State *L) { - struct engine *engine = engine_luaget(L); - knot_rrset_t *opt_rr = engine->resolver.opt_rr; - if (!lua_isnumber(L, 1)) { - lua_pushinteger(L, knot_edns_get_payload(opt_rr)); - return 1; + struct kr_context *ctx = &the_worker->engine->resolver; + const int argc = lua_gettop(L); + if (argc == 0) { + lua_pushinteger(L, knot_edns_get_payload(ctx->downstream_opt_rr)); + lua_pushinteger(L, knot_edns_get_payload(ctx->upstream_opt_rr)); + return 2; + } + + if (argc == 1) { + int bufsize = lua_tointeger(L, 1); + if (bufsize < 512 || bufsize > UINT16_MAX) + lua_error_p(L, "bufsize must be within <512, " STR(UINT16_MAX) ">"); + knot_edns_set_payload(ctx->downstream_opt_rr, (uint16_t)bufsize); + knot_edns_set_payload(ctx->upstream_opt_rr, (uint16_t)bufsize); + } else if (argc == 2) { + int bufsize_downstream = lua_tointeger(L, 1); + int bufsize_upstream = lua_tointeger(L, 2); + if (bufsize_downstream < 512 || bufsize_upstream < 512 + || bufsize_downstream > UINT16_MAX || bufsize_upstream > UINT16_MAX) { + lua_error_p(L, "bufsize must be within <512, " STR(UINT16_MAX) ">"); + } + knot_edns_set_payload(ctx->downstream_opt_rr, (uint16_t)bufsize_downstream); + knot_edns_set_payload(ctx->upstream_opt_rr, (uint16_t)bufsize_upstream); } - int bufsize = lua_tointeger(L, 1); - if (bufsize < 512 || bufsize > UINT16_MAX) - lua_error_p(L, "bufsize must be within <512, " STR(UINT16_MAX) ">"); - knot_edns_set_payload(opt_rr, (uint16_t) bufsize); return 0; } @@ -335,11 +406,7 @@ static int net_tls(lua_State *L) { - struct engine *engine = engine_luaget(L); - if (!engine) { - return 0; - } - struct network *net = &engine->net; + struct network *net = &the_worker->engine->net; if (!net) { return 0; } @@ -474,7 +541,7 @@ /* TODO idea: allow starting the lua table with *multiple* IP targets, * meaning the authentication config should be applied to each. */ - struct network *net = &engine_luaget(L)->net; + struct network *net = &the_worker->engine->net; if (lua_gettop(L) == 0) return tls_params2lua(L, net->tls_client_params); /* Various basic sanity-checking. */ @@ -688,7 +755,7 @@ if (!addr) lua_error_p(L, "invalid IP address"); /* Do the actual removal. */ - struct network *net = &engine_luaget(L)->net; + struct network *net = &the_worker->engine->net; int r = tls_client_param_remove(net->tls_client_params, addr); free_const(addr); lua_error_maybe(L, r); @@ -698,18 +765,18 @@ static int net_tls_padding(lua_State *L) { - struct engine *engine = engine_luaget(L); + struct kr_context *ctx = &the_worker->engine->resolver; /* Only return current padding. */ if (lua_gettop(L) == 0) { - if (engine->resolver.tls_padding < 0) { + if (ctx->tls_padding < 0) { lua_pushboolean(L, true); return 1; - } else if (engine->resolver.tls_padding == 0) { + } else if (ctx->tls_padding == 0) { lua_pushboolean(L, false); return 1; } - lua_pushinteger(L, engine->resolver.tls_padding); + lua_pushinteger(L, ctx->tls_padding); return 1; } @@ -720,15 +787,15 @@ if (lua_isboolean(L, 1)) { bool x = lua_toboolean(L, 1); if (x) { - engine->resolver.tls_padding = -1; + ctx->tls_padding = -1; } else { - engine->resolver.tls_padding = 0; + ctx->tls_padding = 0; } } else if (lua_isnumber(L, 1)) { int padding = lua_tointeger(L, 1); if ((padding < 0) || (padding > MAX_TLS_PADDING)) lua_error_p(L, "%s", errstr); - engine->resolver.tls_padding = padding; + ctx->tls_padding = padding; } else { lua_error_p(L, "%s", errstr); } @@ -741,7 +808,7 @@ static int net_tls_sticket_secret_string(lua_State *L) { - struct network *net = &engine_luaget(L)->net; + struct network *net = &the_worker->engine->net; size_t secret_len; const char *secret; @@ -807,7 +874,7 @@ } fclose(fp); - struct network *net = &engine_luaget(L)->net; + struct network *net = &the_worker->engine->net; tls_session_ticket_ctx_destroy(net->tls_session_ticket_ctx); net->tls_session_ticket_ctx = @@ -896,17 +963,13 @@ static int net_tcp_in_idle(lua_State *L) { - struct engine *engine = engine_luaget(L); - struct network *net = &engine->net; - + struct network *net = &the_worker->engine->net; return net_update_timeout(L, &net->tcp.in_idle_timeout, "net.tcp_in_idle"); } static int net_tls_handshake_timeout(lua_State *L) { - struct engine *engine = engine_luaget(L); - struct network *net = &engine->net; - + struct network *net = &the_worker->engine->net; return net_update_timeout(L, &net->tcp.tls_handshake_timeout, "net.tls_handshake_timeout"); } @@ -919,8 +982,6 @@ #if __linux__ - struct engine *engine = engine_luaget(L); - struct network *net = &engine->net; int progfd = lua_tointeger(L, 1); if (progfd == 0) { /* conversion error despite that fact @@ -930,7 +991,7 @@ } lua_pop(L, 1); - if (network_set_bpf(net, progfd) == 0) { + if (network_set_bpf(&the_worker->engine->net, progfd) == 0) { lua_error_p(L, "failed to attach BPF program to some networks: %s", kr_strerror(errno)); } @@ -949,9 +1010,7 @@ #if __linux__ - struct engine *engine = engine_luaget(L); - struct network *net = &engine->net; - network_clear_bpf(net); + network_clear_bpf(&the_worker->engine->net); lua_pushboolean(L, 1); return 1; @@ -970,7 +1029,7 @@ } size_t kind_len; const char *kind = lua_tolstring(L, 1, &kind_len); - struct network *net = &engine_luaget(L)->net; + struct network *net = &the_worker->engine->net; /* Unregistering */ if (param_count == 1) { diff -Nru knot-resolver-5.1.1/daemon/bindings/net_dns_tweaks.rst knot-resolver-5.2.1/daemon/bindings/net_dns_tweaks.rst --- knot-resolver-5.1.1/daemon/bindings/net_dns_tweaks.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/bindings/net_dns_tweaks.rst 2020-12-09 09:44:29.000000000 +0000 @@ -6,19 +6,30 @@ Following settings change low-level details of DNS protocol implementation. Default values should not be changed except for very special cases. -.. function:: net.bufsize([udp_bufsize]) +.. function:: net.bufsize([udp_downstream_bufsize][, udp_upstream_bufsize]) - Get/set maximum EDNS payload size advertised in DNS packets. Default is 4096 bytes and the default will be lowered to value around 1220 bytes in future, once `DNS Flag Day 2020 `_ becomes effective. + Get/set maximum EDNS payload size advertised in DNS packets. Different values can be configured for communication downstream (towards clients) and upstream (towards other DNS servers). Set and also get operations use values in this order. - Minimal value allowed by standard :rfc:`6891` is 512 bytes, which is equal to DNS packet size without Extension Mechanisms for DNS. Value 1220 bytes is minimum size required in DNSSEC standard :rfc:`4035`. + Default is 1232 bytes which was chosed to minimize risk of `issues caused by IP fragmentation `_. Further details can be found at `DNS Flag Day 2020 `_ web site. + + Minimal value allowed by standard :rfc:`6891` is 512 bytes, which is equal to DNS packet size without Extension Mechanisms for DNS. Value 1220 bytes is minimum size required by DNSSEC standard :rfc:`4035`. Example output: .. code-block:: lua + -- set downstream and upstream bufsize to value 4096 > net.bufsize(4096) - nil + -- get configured downstream and upstream bufsizes, respectively + > net.bufsize() + 4096 -- result # 1 + 4096 -- result # 2 + + -- set downstream bufsize to 4096 and upstream bufsize to 1232 + > net.bufsize(4096, 1232) + -- get configured downstream and upstream bufsizes, respectively > net.bufsize() - 4096 + 4096 -- result # 1 + 1232 -- result # 2 .. include:: ../modules/workarounds/README.rst diff -Nru knot-resolver-5.1.1/daemon/bindings/net_server.rst knot-resolver-5.2.1/daemon/bindings/net_server.rst --- knot-resolver-5.1.1/daemon/bindings/net_server.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/bindings/net_server.rst 2020-12-09 09:44:29.000000000 +0000 @@ -13,10 +13,12 @@ :header: "Protocol/service", "net.listen *kind*" "DNS (unencrypted UDP+TCP, :rfc:`1034`)","``dns``" - ":ref:`DNS-over-TLS (DoT) `","``tls``" - ":ref:`mod-http-doh`","``doh``" + "DNS (unencrypted UDP, :ref:`using XDP Linux API `)","``xdp``" + ":ref:`dns-over-tls`","``tls``" + ":ref:`dns-over-https`","``doh2``" ":ref:`Web management `","``webmgmt``" ":ref:`Control socket `","``control``" + ":ref:`mod-http-doh`","``doh``" .. note:: By default, **unencrypted DNS and DNS-over-TLS** are configured to **listen on localhost**. @@ -37,8 +39,9 @@ :header: "**Network protocol**", "**Configuration command**" "DNS (UDP+TCP, :rfc:`1034`)","``net.listen('192.0.2.123', 53)``" - ":ref:`DNS-over-TLS (DoT) `","``net.listen('192.0.2.123', 853, { kind = 'tls' })``" - ":ref:`mod-http-doh`","``net.listen('192.0.2.123', 443, { kind = 'doh' })``" + "DNS (UDP, :ref:`using XDP `)","``net.listen('192.0.2.123', 53, { kind = 'xdp' })``" + ":ref:`dns-over-tls`","``net.listen('192.0.2.123', 853, { kind = 'tls' })``" + ":ref:`dns-over-https`","``net.listen('192.0.2.123', 443, { kind = 'doh2' })``" ":ref:`Web management `","``net.listen('192.0.2.123', 8453, { kind = 'webmgmt' })``" ":ref:`Control socket `","``net.listen('/tmp/kres.control', nil, { kind = 'control' })``" @@ -52,12 +55,11 @@ net.listen(net.eth0, 853, { kind = 'tls' }) net.listen('192.0.2.1', 53, { freebind = true }) net.listen({'127.0.0.1', '::1'}, 53, { kind = 'dns' }) - net.listen('::', 443, { kind = 'doh' }) -- see http module + net.listen('::', 443, { kind = 'doh2' }) net.listen('::', 8453, { kind = 'webmgmt' }) -- see http module net.listen('/tmp/kresd-socket', nil, { kind = 'webmgmt' }) -- http module supports AF_UNIX - -.. warning:: Make sure you read section :ref:`mod-http-doh` before exposing - the DNS-over-HTTP protocol to outside. + net.listen('eth0', 53, { kind = 'xdp' }) + net.listen('192.0.2.123', 53, { kind = 'xdp', nic_queue = 0 }) .. warning:: On machines with multiple IP addresses avoid listening on wildcards ``0.0.0.0`` or ``::``. Knot Resolver could answer from different IP @@ -111,6 +113,16 @@ [protocol] => tcp } } + [4] => { + [kind] => xdp + [transport] => { + [family] => inet4+inet6 + [interface] => eth2 + [nic_queue] => 0 + [port] => 53 + [protocol] => udp + } + } .. function:: net.interfaces() @@ -151,3 +163,4 @@ .. _`dnsproxy module`: https://www.knot-dns.cz/docs/2.7/html/modules.html#dnsproxy-tiny-dns-proxy + diff -Nru knot-resolver-5.1.1/daemon/bindings/net_tlssrv.rst knot-resolver-5.2.1/daemon/bindings/net_tlssrv.rst --- knot-resolver-5.1.1/daemon/bindings/net_tlssrv.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/bindings/net_tlssrv.rst 2020-12-09 09:44:29.000000000 +0000 @@ -2,9 +2,8 @@ .. _tls-server-config: -DNS-over-TLS server (DoT) -------------------------- -DoT encrypts DNS traffic with Transport Security Layer protocol and thus protects DNS traffic from certain types of attacks. +DoT and DoH (encrypted DNS) +--------------------------- .. warning:: @@ -14,17 +13,77 @@ See `slides `_ or `the article itself `_. -DNS-over-TLS server (:rfc:`7858`) is enabled by default on localhost. -Information how to configure listening on specific IP addresses is in previous sections: +DoT and DoH encrypt DNS traffic with Transport Layer Security (TLS) protocol +and thus protects DNS traffic from certain types of attacks. + +You can learn more about DoT and DoH and their implementation in Knot Resolver +in `this article +`_. + +.. _dns-over-tls: + +DNS-over-TLS (DoT) +^^^^^^^^^^^^^^^^^^ + +DNS-over-TLS server (:rfc:`7858`) can be configured using ``tls`` kind in +:func:`net.listen()`. It is enabled on localhost by default. + +For certificate configuration, refer to :ref:`dot-doh-config-options`. + +.. _dns-over-https: + +DNS-over-HTTPS (DoH) +^^^^^^^^^^^^^^^^^^^^ + +.. note:: Knot Resolver currently offers two DoH implementations. It is + recommended to use this new implementation, which is more reliable, scalable + and has fewer dependencies. Make sure to use ``doh2`` kind in + :func:`net.listen()` to select this implementation. + +.. tip:: Independent information about political controversies around the + DoH deployment by default can be found in blog posts `DNS Privacy at IETF + 104 `_ and `More DOH + `_ by Geoff Huston and + `Centralised DoH is bad for Privacy, in 2019 and beyond + `_ + by Bert Hubert. + +DNS-over-HTTPS server (:rfc:`8484`) can be configured using ``doh2`` kind in :func:`net.listen()`. -By default a self-signed certificate is generated. For serious deployments +This implementation supports HTTP/2 (:rfc:`7540`). Queries can be sent to the +``/dns-query`` endpoint, e.g.: + +.. code-block:: bash + + $ kdig @127.0.0.1 +https www.knot-resolver.cz AAAA + +**Only TLS version 1.3 (or higher) is supported with DNS-over-HTTPS.** The +additional considerations for TLS 1.2 required by HTTP/2 are not implemented +(:rfc:`7540#section-9.2`). + +.. warning:: Take care when configuring your server to listen on well known + HTTPS port. If an unrelated HTTPS service is running on the same port with + REUSEPORT enabled, you will end up with both services malfunctioning. + +.. _dot-doh-config-options: + +Configuration options +^^^^^^^^^^^^^^^^^^^^^ + +.. note:: These settings affect both DNS-over-TLS and DNS-over-HTTPS (except + the legacy implementation). + +A self-signed certificate is generated by default. For serious deployments it is strongly recommended to configure your own TLS certificates signed by a trusted CA. This is done using function :c:func:`net.tls()`. .. function:: net.tls([cert_path], [key_path]) - Get/set path to a server TLS certificate and private key for DNS/TLS. + When called with path arguments, the function loads the server TLS + certificate and private key for DoT and DoH. + + When called without arguments, the command returns the currently configured paths. Example output: @@ -34,15 +93,10 @@ > net.tls() -- print configured paths ("/etc/knot-resolver/server-cert.pem", "/etc/knot-resolver/server-key.pem") -.. function:: net.tls_padding([true | false]) - - Get/set EDNS(0) padding of answers to queries that arrive over TLS - transport. If set to `true` (the default), it will use a sensible - default padding scheme, as implemented by libknot if available at - compile time. If set to a numeric value >= 2 it will pad the - answers to nearest *padding* boundary, e.g. if set to `64`, the - answer will have size of a multiple of 64 (64, 128, 192, ...). If - set to `false` (or a number < 2), it will disable padding entirely. + .. tip:: The certificate files aren't automatically reloaded on change. If + you update the certificate files, e.g. using ACME, you have to either + restart the service(s) or call this function again using + :ref:`control-sockets`. .. function:: net.tls_sticket_secret([string with pre-shared secret]) @@ -72,3 +126,14 @@ The same as :func:`net.tls_sticket_secret`, except the secret is read from a (binary) file. + +.. function:: net.tls_padding([true | false]) + + Get/set EDNS(0) padding of answers to queries that arrive over TLS + transport. If set to `true` (the default), it will use a sensible + default padding scheme, as implemented by libknot if available at + compile time. If set to a numeric value >= 2 it will pad the + answers to nearest *padding* boundary, e.g. if set to `64`, the + answer will have size of a multiple of 64 (64, 128, 192, ...). If + set to `false` (or a number < 2), it will disable padding entirely. + diff -Nru knot-resolver-5.1.1/daemon/bindings/net_xdpsrv.rst knot-resolver-5.2.1/daemon/bindings/net_xdpsrv.rst --- knot-resolver-5.1.1/daemon/bindings/net_xdpsrv.rst 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/bindings/net_xdpsrv.rst 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,138 @@ +.. SPDX-License-Identifier: GPL-3.0-or-later + +.. _dns-over-xdp: + +XDP for higher UDP performance +------------------------------ + +.. warning:: + As of version 5.2.0, XDP support in Knot Resolver is considered + experimental. The impact on overall throughput and performance may not + always be beneficial. + +Using XDP allows significant speedup of UDP packet processing in recent Linux kernels, +especially with some network drivers that implement good support. +The basic idea is that for selected packets the Linux networking stack is bypassed, +and some drivers can even directly use the user-space buffers for reading and writing. + +.. TODO perhaps some hint/link about how significant speedup one might get? (link to some talk video?) + +Prerequisites +^^^^^^^^^^^^^ +.. this is mostly copied from knot-dns doc/operations.rst + +.. warning:: + Bypassing the network stack has significant implications, such as bypassing the firewall + and monitoring solutions. + Make sure you're familiar with the trade-offs before using this feature. + Read more in :ref:`dns-over-xdp_limitations`. + +* Linux kernel 4.18+ (5.x+ is recommended for optimal performance) compiled with + the `CONFIG_XDP_SOCKETS=y` option. XDP isn't supported in other operating systems. +* libknot compiled with XDP support +* **A multiqueue network card with native XDP support is highly recommended**, + otherwise the performance gain will be much lower and you may encounter + issues due to XDP emulation. + Successfully tested cards: + + * Intel series 700 (driver `i40e`), maximum number of queues per interface is 64. + * Intel series 500 (driver `ixgbe`), maximum number of queues per interface is 64. + The number of CPUs available has to be at most 64! + + +Set up +^^^^^^ +.. first parts are mostly copied from knot-dns doc/operations.rst + +The server instances need additional Linux **capabilities** during startup. +(Or you could start them as `root`.) +Execute command + +.. code-block:: bash + + systemctl edit kresd@.service + +And insert these lines: + +.. code-block:: ini + + [Service] + CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_ADMIN CAP_SYS_RESOURCE + AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_ADMIN CAP_SYS_RESOURCE + +.. TODO suggest some way for ethtool -L? Perhaps via systemd units? + +You want the same number of kresd instances and network **queues** on your card; +you can use ``ethtool -L`` before the services start. +With XDP this is more important than with vanilla UDP, as we only support one instance +per queue and unclaimed queues will fall back to vanilla UDP. +Ideally you can set these numbers as high as the number of CPUs that you want kresd to use. + +Modification of ``/etc/knot-resolver/kresd.conf`` may often be quite simple, for example: + +.. code-block:: lua + + net.listen('eth2', 53, { kind = 'xdp' }) + net.listen('203.0.113.53', 53, { kind = 'dns' }) + +Note that you want to also keep the vanilla DNS line to service TCP +and possibly any fallback UDP (e.g. from unclaimed queues). +XDP listening is in principle done on queues of whole network interfaces +and the target addresses of incoming packets aren't checked in any way, +but you are still allowed to specify interface by an address +(if it's unambiguous at that moment): + +.. code-block:: lua + + net.listen('203.0.113.53', 53, { kind = 'xdp' }) + net.listen('203.0.113.53', 53, { kind = 'dns' }) + +The default selection of queues is tailored for the usual naming convention: +``kresd@1.service``, ``kresd@2.service``, ... +but you can still specify them explicitly, e.g. the default is effectively the same as: + +.. code-block:: lua + + net.listen('eth2', 53, { kind = 'xdp', nic_queue = env.SYSTEMD_INSTANCE - 1 }) + + +Optimizations +^^^^^^^^^^^^^ +.. this is basically copied from knot-dns doc/operations.rst + +Some helpful commands: + +.. code-block:: text + + ethtool -N rx-flow-hash udp4 sdfn + ethtool -N rx-flow-hash udp6 sdfn + ethtool -L combined + ethtool -G rx tx + renice -n 19 -p $(pgrep '^ksoftirqd/[0-9]*$') + +.. TODO CPU affinities? `CPUAffinity=%i` in systemd unit sounds good. + + +.. _dns-over-xdp_limitations: + +Limitations +^^^^^^^^^^^ +.. this is basically copied from knot-dns doc/operations.rst + +* VLAN segmentation is not supported. +* MTU higher than 1792 bytes is not supported. +* Multiple BPF filters per one network device are not supported. +* Symmetrical routing is required (query source MAC/IP addresses and + reply destination MAC/IP addresses are the same). +* Systems with big-endian byte ordering require special recompilation of libknot. +* IPv4 header and UDP checksums are not verified on received DNS messages. +* DNS over XDP traffic is not visible to common system tools (e.g. firewall, tcpdump etc.). +* BPF filter is not automatically unloaded from the network device. Manual filter unload:: + + ip link set dev xdp off + +* Knot Resolver only supports using XDP towards clients currently (not towards upstreams). +* When starting up an XDP socket you may get a harmless warning:: + + libbpf: Kernel error message: XDP program already attached + diff -Nru knot-resolver-5.1.1/daemon/bindings/worker.c knot-resolver-5.2.1/daemon/bindings/worker.c --- knot-resolver-5.1.1/daemon/bindings/worker.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/bindings/worker.c 2020-12-09 09:44:29.000000000 +0000 @@ -4,7 +4,6 @@ #include "daemon/bindings/impl.h" -#include "daemon/worker.h" static inline double getseconds(uv_timeval_t *tv) { @@ -38,6 +37,14 @@ lua_setfield(L, -2, "ipv4"); lua_pushnumber(L, worker->stats.ipv6); lua_setfield(L, -2, "ipv6"); + lua_pushnumber(L, worker->stats.err_udp); + lua_setfield(L, -2, "err_udp"); + lua_pushnumber(L, worker->stats.err_tcp); + lua_setfield(L, -2, "err_tcp"); + lua_pushnumber(L, worker->stats.err_tls); + lua_setfield(L, -2, "err_tls"); + lua_pushnumber(L, worker->stats.err_http); + lua_setfield(L, -2, "err_http"); /* Add subset of rusage that represents counters. */ uv_rusage_t rusage; diff -Nru knot-resolver-5.1.1/daemon/bindings/worker.rst knot-resolver-5.2.1/daemon/bindings/worker.rst --- knot-resolver-5.1.1/daemon/bindings/worker.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/bindings/worker.rst 2020-12-09 09:44:29.000000000 +0000 @@ -7,11 +7,15 @@ you can see the statistics or schedule new queries. It also contains information about specified worker count and process rank. +.. envvar:: worker.id + + Value from environment variable ``SYSTEMD_INSTANCE``, + or if it is not set, :envvar:`PID ` (string). + .. envvar:: worker.pid Current worker process PID (number). - .. function:: worker.stats() Return table of statistics. See member descriptions in :c:type:`worker_stats`. diff -Nru knot-resolver-5.1.1/daemon/cache.test/clear.test.lua knot-resolver-5.2.1/daemon/cache.test/clear.test.lua --- knot-resolver-5.1.1/daemon/cache.test/clear.test.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/cache.test/clear.test.lua 2020-12-09 09:44:29.000000000 +0000 @@ -37,18 +37,7 @@ trust_anchors.remove('.') trust_anchors.add('. IN DS 48409 8 2 3D63A0C25BCE86621DE63636F11B35B908EFE8E9381E0E3E9DEFD89EA952C27D') -local function check_answer(desc, qname, qtype, expected_rcode) - qtype_str = kres.tostring.type[qtype] - callback = function(pkt) - same(pkt:rcode(), expected_rcode, - desc .. ': expecting answer for query ' .. qname .. ' ' .. qtype_str - .. ' with rcode ' .. kres.tostring.rcode[expected_rcode]) - - ok((pkt:ancount() > 0) == (pkt:rcode() == kres.rcode.NOERROR), - desc ..': checking number of answers for ' .. qname .. ' ' .. qtype_str) - end - resolve(qname, qtype, kres.class.IN, {}, callback) -end +local check_answer = require('test_utils').check_answer -- do not attempt to contact outside world, operate only on cache net.ipv4 = false @@ -197,17 +186,29 @@ is(cache.count(), 0, 'cache is empty after full clear') end +local function test_cache_used(lower, upper) + return function() + local usage = cache.stats().usage_percent + ok(usage >= lower and usage <= upper, string.format('cache percentage usage is between <%d, %d>', lower, upper)) + end +end + return { + test_cache_used(0, 1), import_zone, + test_cache_used(11, 12), test_exact_match_qtype, test_exact_match_qname, test_callback, import_zone, test_subtree, + test_cache_used(10, 11), test_subtree_limit, + test_cache_used(5, 6), test_apex, import_zone, test_root, import_zone, test_complete_flush, + test_cache_used(0, 1), } diff -Nru knot-resolver-5.1.1/daemon/cache.test/insert_ns.test.integr/deckard.yaml knot-resolver-5.2.1/daemon/cache.test/insert_ns.test.integr/deckard.yaml --- knot-resolver-5.1.1/daemon/cache.test/insert_ns.test.integr/deckard.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/cache.test/insert_ns.test.integr/deckard.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -4,8 +4,7 @@ - name: kresd binary: kresd additional: - - -f - - "1" + - --noninteractive templates: - daemon/cache.test/insert_ns.test.integr/kresd_config.j2 - tests/integration/hints_zone.j2 diff -Nru knot-resolver-5.1.1/daemon/engine.c knot-resolver-5.2.1/daemon/engine.c --- knot-resolver-5.1.1/daemon/engine.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/engine.c 2020-12-09 09:44:29.000000000 +0000 @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -21,6 +22,7 @@ #include "kresconfig.h" #include "daemon/engine.h" #include "daemon/ffimodule.h" +#include "daemon/worker.h" #include "lib/nsrep.h" #include "lib/cache/api.h" #include "lib/defines.h" @@ -35,7 +37,7 @@ #define LRU_REP_SIZE (LRU_RTT_SIZE / 4) /**< NS reputation cache size */ #endif #ifndef LRU_COOKIES_SIZE - #ifdef ENABLE_COOKIES + #if ENABLE_COOKIES #define LRU_COOKIES_SIZE LRU_RTT_SIZE /**< DNS cookies cache size. */ #else #define LRU_COOKIES_SIZE LRU_ASSOC /* simpler than guards everywhere */ @@ -44,7 +46,7 @@ /**@internal Maximum number of incomplete TCP connections in queue. * Default is from empirical testing - in our case, more isn't necessarily better. -* See https://gitlab.labs.nic.cz/knot/knot-resolver/-/merge_requests/968 +* See https://gitlab.nic.cz/knot/knot-resolver/-/merge_requests/968 * */ #ifndef TCP_BACKLOG_DEFAULT #define TCP_BACKLOG_DEFAULT 128 @@ -135,7 +137,7 @@ /** Quit current executable. */ static int l_quit(lua_State *L) { - engine_stop(engine_luaget(L)); + engine_stop(the_worker->engine); return 0; } @@ -184,7 +186,7 @@ /** Return hostname. */ static int l_hostname(lua_State *L) { - struct engine *engine = engine_luaget(L); + struct engine *engine = the_worker->engine; if (lua_gettop(L) == 0) { lua_pushstring(L, engine_get_hostname(engine)); return 1; @@ -209,8 +211,7 @@ /** Load root hints from zonefile. */ static int l_hint_root_file(lua_State *L) { - struct engine *engine = engine_luaget(L); - struct kr_context *ctx = &engine->resolver; + struct kr_context *ctx = &the_worker->engine->resolver; const char *file = lua_tostring(L, 1); const char *err = engine_hint_root_file(ctx, file); @@ -371,63 +372,6 @@ return 1; } -/** @internal Throw Lua error if expr is false */ -#define expr_checked(expr) \ - if (!(expr)) { lua_pushboolean(L, false); lua_rawseti(L, -2, lua_objlen(L, -2) + 1); continue; } - -static int l_map(lua_State *L) -{ - /* We don't kr_log_deprecate() here for now. Plan: after --forks gets *removed*, - * kill internal uses of map() (e.g. from daf module) and add deprecation here. - * Alternatively we might (attempt to) implement map() in another way. */ - if (lua_gettop(L) != 1 || !lua_isstring(L, 1)) - lua_error_p(L, "map('string with a lua expression')"); - - struct engine *engine = engine_luaget(L); - const char *cmd = lua_tostring(L, 1); - uint32_t len = strlen(cmd); - lua_newtable(L); - - /* Execute on leader instance */ - int ntop = lua_gettop(L); - engine_cmd(L, cmd, true); - lua_settop(L, ntop + 1); /* Push only one return value to table */ - lua_rawseti(L, -2, 1); - - for (size_t i = 0; i < engine->ipc_set.len; ++i) { - int fd = engine->ipc_set.at[i]; - /* Send command */ - expr_checked(write(fd, &len, sizeof(len)) == sizeof(len)); - expr_checked(write(fd, cmd, len) == len); - /* Read response */ - uint32_t rlen = 0; - if (read(fd, &rlen, sizeof(rlen)) == sizeof(rlen)) { - expr_checked(rlen < UINT32_MAX); - auto_free char *rbuf = malloc(rlen + 1); - expr_checked(rbuf != NULL); - expr_checked(read(fd, rbuf, rlen) == rlen); - rbuf[rlen] = '\0'; - /* Unpack from JSON */ - JsonNode *root_node = json_decode(rbuf); - if (root_node) { - l_unpack_json(L, root_node); - } else { - lua_pushlstring(L, rbuf, rlen); - } - json_delete(root_node); - lua_rawseti(L, -2, lua_objlen(L, -2) + 1); - continue; - } - /* Didn't respond */ - lua_pushboolean(L, false); - lua_rawseti(L, -2, lua_objlen(L, -2) + 1); - } - return 1; -} - -#undef expr_checked - - /* * Engine API. */ @@ -442,11 +386,13 @@ engine->resolver.modules = &engine->modules; engine->resolver.cache_rtt_tout_retry_interval = KR_NS_TIMEOUT_RETRY_INTERVAL; /* Create OPT RR */ - engine->resolver.opt_rr = mm_alloc(engine->pool, sizeof(knot_rrset_t)); - if (!engine->resolver.opt_rr) { + engine->resolver.downstream_opt_rr = mm_alloc(engine->pool, sizeof(knot_rrset_t)); + engine->resolver.upstream_opt_rr = mm_alloc(engine->pool, sizeof(knot_rrset_t)); + if (!engine->resolver.downstream_opt_rr || !engine->resolver.upstream_opt_rr) { return kr_error(ENOMEM); } - knot_edns_init(engine->resolver.opt_rr, KR_EDNS_PAYLOAD, 0, KR_EDNS_VERSION, engine->pool); + knot_edns_init(engine->resolver.downstream_opt_rr, KR_EDNS_PAYLOAD, 0, KR_EDNS_VERSION, engine->pool); + knot_edns_init(engine->resolver.upstream_opt_rr, KR_EDNS_PAYLOAD, 0, KR_EDNS_VERSION, engine->pool); /* Use default TLS padding */ engine->resolver.tls_padding = -1; /* Empty init; filled via ./lua/postconfig.lua */ @@ -496,10 +442,6 @@ lua_setglobal(engine->L, "tojson"); lua_pushcfunction(engine->L, l_fromjson); lua_setglobal(engine->L, "fromjson"); - lua_pushcfunction(engine->L, l_map); - lua_setglobal(engine->L, "map"); - lua_pushlightuserdata(engine->L, engine); - lua_setglobal(engine->L, "__engine"); /* Random number generator */ lua_getfield(engine->L, LUA_GLOBALSINDEX, "math"); lua_getfield(engine->L, -1, "randomseed"); @@ -629,9 +571,6 @@ * e.g. the endpoint kind registry to work (inside ->net), * and this registry deinitization uses the lua state. */ network_close_force(&engine->net); - for (size_t i = 0; i < engine->ipc_set.len; ++i) { - close(engine->ipc_set.at[i]); - } for (size_t i = 0; i < engine->modules.len; ++i) { engine_unload(engine, engine->modules.at[i]); } @@ -650,7 +589,6 @@ /* Free data structures */ array_clear(engine->modules); array_clear(engine->backends); - array_clear(engine->ipc_set); kr_ta_clear(&engine->resolver.trust_anchors); kr_ta_clear(&engine->resolver.negative_anchors); free(engine->hostname); @@ -676,22 +614,6 @@ return engine_pcall(L, 2); } -int engine_ipc(struct engine *engine, const char *expr) -{ - if (engine == NULL || engine->L == NULL) { - return kr_error(ENOEXEC); - } - - /* Run expression and serialize response. */ - engine_cmd(engine->L, expr, true); - if (lua_gettop(engine->L) > 0) { - l_tojson(engine->L); - return 1; - } else { - return 0; - } -} - int engine_load_sandbox(struct engine *engine) { /* Init environment */ @@ -858,11 +780,3 @@ return kr_error(ENOENT); } -struct engine *engine_luaget(lua_State *L) -{ - lua_getglobal(L, "__engine"); - struct engine *engine = lua_touserdata(L, -1); - if (!engine) luaL_error(L, "internal error, empty engine pointer"); - lua_pop(L, 1); - return engine; -} diff -Nru knot-resolver-5.1.1/daemon/engine.h knot-resolver-5.2.1/daemon/engine.h --- knot-resolver-5.1.1/daemon/engine.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/engine.h 2020-12-09 09:44:29.000000000 +0000 @@ -13,15 +13,11 @@ #include "lib/resolve.h" #include "daemon/network.h" -/* @internal Array of file descriptors shorthand. */ -typedef array_t(int) fd_array_t; - struct engine { struct kr_context resolver; struct network net; module_array_t modules; array_t(const struct kr_cdb_api *) backends; - fd_array_t ipc_set; knot_mm_t *pool; char *hostname; struct lua_State *L; @@ -40,9 +36,6 @@ /** Execute current chunk in the sandbox */ int engine_pcall(struct lua_State *L, int argc); -int engine_ipc(struct engine *engine, const char *expr); - - int engine_load_sandbox(struct engine *engine); int engine_loadconf(struct engine *engine, const char *config_path); @@ -52,9 +45,6 @@ int engine_register(struct engine *engine, const char *name, const char *precedence, const char* ref); int engine_unregister(struct engine *engine, const char *name); -/** Return engine light userdata. */ -struct engine *engine_luaget(struct lua_State *L); - /** Set/get the per engine hostname */ char *engine_get_hostname(struct engine *engine); int engine_set_hostname(struct engine *engine, const char *hostname); diff -Nru knot-resolver-5.1.1/daemon/http.c knot-resolver-5.2.1/daemon/http.c --- knot-resolver-5.1.1/daemon/http.c 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/http.c 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,667 @@ +/* + * Copyright (C) 2020 CZ.NIC, z.s.p.o + * + * Initial Author: Jan Hák + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include +#include + +#include "daemon/io.h" +#include "daemon/http.h" +#include "daemon/worker.h" +#include "daemon/session.h" +#include "lib/layer/iterate.h" /* kr_response_classify */ +#include "lib/cache/util.h" + +#include "contrib/cleanup.h" +#include "contrib/base64url.h" + +#define MAKE_NV(K, KS, V, VS) \ + { (uint8_t *)(K), (uint8_t *)(V), (KS), (VS), NGHTTP2_NV_FLAG_NONE } + +#define MAKE_STATIC_NV(K, V) \ + MAKE_NV((K), sizeof(K) - 1, (V), sizeof(V) - 1) + +/* Use same maximum as for tcp_pipeline_max. */ +#define HTTP_MAX_CONCURRENT_STREAMS UINT16_MAX + +#define HTTP_FRAME_HDLEN 9 +#define HTTP_FRAME_PADLEN 1 + +#define MAX_DECIMAL_LENGTH(VT) ((CHAR_BIT * sizeof(VT) / 3) + 3) + +struct http_data { + uint8_t *buf; + size_t len; + size_t pos; + uint32_t ttl; + uv_write_cb on_write; + uv_write_t *req; +}; + +/* + * Write HTTP/2 protocol data to underlying transport layer. + */ +static ssize_t send_callback(nghttp2_session *h2, const uint8_t *data, size_t length, + int flags, void *user_data) +{ + struct http_ctx *ctx = (struct http_ctx *)user_data; + return ctx->send_cb(data, length, ctx->session); +} + +/* + * Send padding length (if greater than zero). + */ +static int send_padlen(struct http_ctx *ctx, size_t padlen) +{ + int ret; + uint8_t buf; + + if (padlen == 0) + return 0; + + buf = (uint8_t)padlen; + ret = ctx->send_cb(&buf, HTTP_FRAME_PADLEN, ctx->session); + if (ret < 0) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + return 0; +} + +/* + * Send HTTP/2 zero-byte padding. + * + * This sends only padlen-1 bytes of padding (if any), since padlen itself + * (already sent) is also considered padding. Refer to RFC7540, section 6.1 + */ +static int send_padding(struct http_ctx *ctx, uint8_t padlen) +{ + static const uint8_t buf[UINT8_MAX]; + int ret; + + if (padlen <= 1) + return 0; + + ret = ctx->send_cb(buf, padlen - 1, ctx->session); + if (ret < 0) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + return 0; +} + +/* + * Write entire DATA frame to underlying transport layer. + * + * This function reads directly from data provider to avoid copying packet wire buffer. + */ +static int send_data_callback(nghttp2_session *h2, nghttp2_frame *frame, const uint8_t *framehd, + size_t length, nghttp2_data_source *source, void *user_data) +{ + struct http_data *data; + int ret; + struct http_ctx *ctx; + + ctx = (struct http_ctx *)user_data; + data = (struct http_data*)source->ptr; + + ret = ctx->send_cb(framehd, HTTP_FRAME_HDLEN, ctx->session); + if (ret < 0) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + ret = send_padlen(ctx, frame->data.padlen); + if (ret < 0) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + ret = ctx->send_cb(data->buf + data->pos, length, ctx->session); + if (ret < 0) + return NGHTTP2_ERR_CALLBACK_FAILURE; + data->pos += length; + assert(data->pos <= data->len); + + ret = send_padding(ctx, (uint8_t)frame->data.padlen); + if (ret < 0) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + return 0; +} + +/* + * Check endpoint and uri path + */ +static int check_uri(const char* uri_path) +{ + static const char key[] = "dns="; + static const char *delim = "&"; + static const char *endpoins[] = {"dns-query", "doh"}; + char *beg; + char *end_prev; + ssize_t endpoint_len; + ssize_t ret; + + if (!uri_path) + return kr_error(EINVAL); + + auto_free char *path = malloc(sizeof(*path) * (strlen(uri_path) + 1)); + if (!path) + return kr_error(ENOMEM); + + memcpy(path, uri_path, strlen(uri_path)); + path[strlen(uri_path)] = '\0'; + + char *query_mark = strstr(path, "?"); + + /* calculating of endpoint_len - for POST or GET method */ + endpoint_len = (query_mark) ? query_mark - path - 1 : strlen(path) - 1; + + /* check endpoint */ + ret = -1; + for(int i = 0; i < sizeof(endpoins)/sizeof(*endpoins); i++) + { + if (strlen(endpoins[i]) != endpoint_len) + continue; + ret = strncmp(path + 1, endpoins[i], strlen(endpoins[i])); + if (!ret) + break; + } + + if (ret) /* no endpoint found */ + return -1; + if (endpoint_len == strlen(path) - 1) /* done for POST method */ + return 0; + + /* go over key:value pair */ + beg = strtok(query_mark + 1, delim); + if (beg) { + while (beg != NULL) { + if (!strncmp(beg, key, 4)) { /* dns variable in path found */ + break; + } + end_prev = beg + strlen(beg); + beg = strtok(NULL, delim); + if (beg-1 != end_prev) { /* detect && */ + return -1; + } + } + + if (!beg) { /* no dns variable in path */ + return -1; + } + } + + return 0; +} + +/* + * Process a query from URI path if there's base64url encoded dns variable. + */ +static int process_uri_path(struct http_ctx *ctx, const char* path, int32_t stream_id) +{ + if (!ctx || !path) + return kr_error(EINVAL); + + static const char key[] = "dns="; + char *beg = strstr(path, key); + char *end; + size_t remaining; + ssize_t ret; + uint8_t *dest; + + if (!beg) /* No dns variable in path. */ + return 0; + + beg += sizeof(key) - 1; + end = strchr(beg, '&'); + if (end == NULL) + end = beg + strlen(beg); + + ctx->buf_pos = sizeof(uint16_t); /* Reserve 2B for dnsmsg len. */ + remaining = ctx->buf_size - ctx->submitted - ctx->buf_pos; + dest = ctx->buf + ctx->buf_pos; + + ret = kr_base64url_decode((uint8_t*)beg, end - beg, dest, remaining); + if (ret < 0) { + ctx->buf_pos = 0; + kr_log_verbose("[http] base64url decode failed %s\n", + strerror(ret)); + return ret; + } + + ctx->buf_pos += ret; + queue_push(ctx->streams, stream_id); + return 0; +} + +static void refuse_stream(nghttp2_session *h2, int32_t stream_id) +{ + nghttp2_submit_rst_stream( + h2, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_REFUSED_STREAM); +} + +/* + * Save stream id from first header's frame. + * + * We don't support interweaving from different streams. To successfully parse + * multiple subsequent streams, each one must be fully received before processing + * a new stream. + */ +static int begin_headers_callback(nghttp2_session *h2, const nghttp2_frame *frame, + void *user_data) +{ + struct http_ctx *ctx = (struct http_ctx *)user_data; + int32_t stream_id = frame->hd.stream_id; + + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + + if (ctx->incomplete_stream != -1) { + kr_log_verbose( + "[http] stream %d incomplete, refusing\n", ctx->incomplete_stream); + refuse_stream(h2, stream_id); + } else { + ctx->incomplete_stream = stream_id; + } + return 0; +} + +/* + * Process a received header name-value pair. + * + * In DoH, GET requests contain the base64url-encoded query in dns variable present in path. + * This variable is parsed from :path pseudoheader. + */ +static int header_callback(nghttp2_session *h2, const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, const uint8_t *value, + size_t valuelen, uint8_t flags, void *user_data) +{ + struct http_ctx *ctx = (struct http_ctx *)user_data; + int32_t stream_id = frame->hd.stream_id; + + if (frame->hd.type != NGHTTP2_HEADERS) + return 0; + + if (ctx->incomplete_stream != stream_id) { + kr_log_verbose( + "[http] stream %d incomplete, refusing\n", ctx->incomplete_stream); + refuse_stream(h2, stream_id); + return 0; + } + + if (!strcasecmp(":path", (const char *)name)) { + if (check_uri((const char *)value) < 0) { + refuse_stream(h2, stream_id); + return 0; + } + + ctx->uri_path = malloc(sizeof(*ctx->uri_path) * (valuelen + 1)); + if (!ctx->uri_path) + return kr_error(ENOMEM); + memcpy(ctx->uri_path, value, valuelen); + ctx->uri_path[valuelen] = '\0'; + } + + if (!strcasecmp(":method", (const char *)name)) { + if (!strcasecmp("get", (const char *)value)) { + ctx->current_method = HTTP_METHOD_GET; + } else if (!strcasecmp("post", (const char *)value)) { + ctx->current_method = HTTP_METHOD_POST; + } else { + ctx->current_method = HTTP_METHOD_NONE; + } + } + + return 0; +} + +/* + * Process DATA chunk sent by the client (by POST method). + * + * We use a single DNS message buffer for the entire connection. Therefore, we + * don't support interweaving DATA chunks from different streams. To successfully + * parse multiple subsequent streams, each one must be fully received before + * processing a new stream. See https://gitlab.nic.cz/knot/knot-resolver/-/issues/619 + */ +static int data_chunk_recv_callback(nghttp2_session *h2, uint8_t flags, int32_t stream_id, + const uint8_t *data, size_t len, void *user_data) +{ + struct http_ctx *ctx = (struct http_ctx *)user_data; + ssize_t remaining; + ssize_t required; + bool is_first = queue_len(ctx->streams) == 0 || queue_tail(ctx->streams) != ctx->incomplete_stream; + + if (ctx->incomplete_stream != stream_id) { + kr_log_verbose( + "[http] stream %d incomplete, refusing\n", + ctx->incomplete_stream); + refuse_stream(h2, stream_id); + ctx->incomplete_stream = -1; + return 0; + } + + remaining = ctx->buf_size - ctx->submitted - ctx->buf_pos; + required = len; + /* First data chunk of the new stream */ + if (is_first) + required += sizeof(uint16_t); + + if (required > remaining) { + kr_log_error("[http] insufficient space in buffer\n"); + ctx->incomplete_stream = -1; + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + if (is_first) { + ctx->buf_pos = sizeof(uint16_t); /* Reserve 2B for dnsmsg len. */ + queue_push(ctx->streams, stream_id); + } + + memcpy(ctx->buf + ctx->buf_pos, data, len); + ctx->buf_pos += len; + return 0; +} + +/* + * Finalize existing buffer upon receiving the last frame in the stream. + * + * For GET, this would be HEADERS frame. + * For POST, it is a DATA frame. + * + * Unrelated frames (such as SETTINGS) are ignored (no data was buffered). + */ +static int on_frame_recv_callback(nghttp2_session *h2, const nghttp2_frame *frame, void *user_data) +{ + struct http_ctx *ctx = (struct http_ctx *)user_data; + ssize_t len; + int32_t stream_id = frame->hd.stream_id; + assert(stream_id != -1); + + if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) && ctx->incomplete_stream == stream_id) { + if (ctx->current_method == HTTP_METHOD_GET) { + if (process_uri_path(ctx, ctx->uri_path, stream_id) < 0) { + refuse_stream(h2, stream_id); + } + free(ctx->uri_path); + ctx->uri_path = NULL; + } + ctx->incomplete_stream = -1; + ctx->current_method = HTTP_METHOD_NONE; + + len = ctx->buf_pos - sizeof(uint16_t); + if (len <= 0 || len > KNOT_WIRE_MAX_PKTSIZE) { + kr_log_verbose("[http] invalid dnsmsg size: %zd B\n", len); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + knot_wire_write_u16(ctx->buf, len); + ctx->submitted += ctx->buf_pos; + ctx->buf += ctx->buf_pos; + ctx->buf_pos = 0; + } + + return 0; +} + +/* + * Call on_write() callback for written (or failed) packet data. + */ +static void on_pkt_write(struct http_data *data, int status) +{ + if (!data || !data->req || !data->on_write) + return; + + data->on_write(data->req, status); +} + +/* + * Cleanup for closed steams. + * + * If any stream_user_data was set, call the on_write callback to allow + * freeing of the underlying data structure. + */ +static int on_stream_close_callback(nghttp2_session *h2, int32_t stream_id, + uint32_t error_code, void *user_data) +{ + struct http_data *data; + + data = nghttp2_session_get_stream_user_data(h2, stream_id); + if (data) + on_pkt_write(data, error_code == 0 ? 0 : kr_error(EIO)); + + return 0; +} + +/* + * Setup and initialize connection with new HTTP/2 context. + */ +struct http_ctx* http_new(struct session *session, http_send_callback send_cb) +{ + if (!session || !send_cb) + return NULL; + + nghttp2_session_callbacks *callbacks; + struct http_ctx *ctx = NULL; + static const nghttp2_settings_entry iv[] = { + { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, HTTP_MAX_CONCURRENT_STREAMS } + }; + + nghttp2_session_callbacks_new(&callbacks); + nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); + nghttp2_session_callbacks_set_send_data_callback(callbacks, send_data_callback); + nghttp2_session_callbacks_set_on_header_callback(callbacks, header_callback); + nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, begin_headers_callback); + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, data_chunk_recv_callback); + nghttp2_session_callbacks_set_on_frame_recv_callback( + callbacks, on_frame_recv_callback); + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + + ctx = calloc(1UL, sizeof(struct http_ctx)); + if (!ctx) + goto finish; + + ctx->send_cb = send_cb; + ctx->session = session; + queue_init(ctx->streams); + ctx->incomplete_stream = -1; + ctx->submitted = 0; + ctx->current_method = HTTP_METHOD_NONE; + ctx->uri_path = NULL; + + nghttp2_session_server_new(&ctx->h2, callbacks, ctx); + nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE, + iv, sizeof(iv)/sizeof(*iv)); +finish: + nghttp2_session_callbacks_del(callbacks); + return ctx; +} + +/* + * Process inbound HTTP/2 data and return number of bytes read into session wire buffer. + * + * This function may trigger outgoing HTTP/2 data, such as stream resets, window updates etc. + */ +ssize_t http_process_input_data(struct session *session, const uint8_t *buf, + ssize_t nread) +{ + struct http_ctx *ctx = session_http_get_server_ctx(session); + ssize_t ret = 0; + + if (!ctx->h2) + return kr_error(ENOSYS); + assert(ctx->session == session); + + ctx->submitted = 0; + ctx->buf = session_wirebuf_get_free_start(session); + ctx->buf_pos = 0; + ctx->buf_size = session_wirebuf_get_free_size(session); + + ret = nghttp2_session_mem_recv(ctx->h2, buf, nread); + if (ret < 0) { + kr_log_verbose("[http] nghttp2_session_mem_recv failed: %s (%zd)\n", + nghttp2_strerror(ret), ret); + return kr_error(EIO); + } + + ret = nghttp2_session_send(ctx->h2); + if (ret < 0) { + kr_log_verbose("[http] nghttp2_session_send failed: %s (%zd)\n", + nghttp2_strerror(ret), ret); + return kr_error(EIO); + } + + return ctx->submitted; +} + +/* + * Provide data from buffer to HTTP/2 library. + * + * To avoid copying the packet wire buffer, we use NGHTTP2_DATA_FLAG_NO_COPY + * and take care of sending entire DATA frames ourselves with nghttp2_send_data_callback. + * + * See https://www.nghttp2.org/documentation/types.html#c.nghttp2_data_source_read_callback + */ +static ssize_t read_callback(nghttp2_session *h2, int32_t stream_id, uint8_t *buf, + size_t length, uint32_t *data_flags, + nghttp2_data_source *source, void *user_data) +{ + struct http_data *data; + size_t avail; + size_t send; + + data = (struct http_data*)source->ptr; + avail = data->len - data->pos; + send = MIN(avail, length); + + if (avail == send) + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + + *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY; + return send; +} + +/* + * Send dns response provided by the HTTTP/2 data provider. + * + * Data isn't guaranteed to be sent immediately due to underlying HTTP/2 flow control. + */ +static int http_send_response(nghttp2_session *h2, int32_t stream_id, + nghttp2_data_provider *prov) +{ + struct http_data *data = (struct http_data*)prov->source.ptr; + int ret; + const char *directive_max_age = "max-age="; + char size[MAX_DECIMAL_LENGTH(data->len)] = { 0 }; + int max_age_len = MAX_DECIMAL_LENGTH(data->ttl) + strlen(directive_max_age); + char max_age[max_age_len]; + int size_len; + + memset(max_age, 0, max_age_len * sizeof(*max_age)); + size_len = snprintf(size, MAX_DECIMAL_LENGTH(data->len), "%zu", data->len); + max_age_len = snprintf(max_age, max_age_len, "%s%u", directive_max_age, data->ttl); + + nghttp2_nv hdrs[] = { + MAKE_STATIC_NV(":status", "200"), + MAKE_STATIC_NV("content-type", "application/dns-message"), + MAKE_NV("content-length", 14, size, size_len), + MAKE_NV("cache-control", 13, max_age, max_age_len), + }; + + ret = nghttp2_submit_response(h2, stream_id, hdrs, sizeof(hdrs)/sizeof(*hdrs), prov); + if (ret != 0) { + kr_log_verbose("[http] nghttp2_submit_response failed: %s\n", nghttp2_strerror(ret)); + return kr_error(EIO); + } + + ret = nghttp2_session_set_stream_user_data(h2, stream_id, (void*)data); + if (ret != 0) { + kr_log_verbose("[http] failed to set stream user data: %s\n", nghttp2_strerror(ret)); + return kr_error(EIO); + } + + ret = nghttp2_session_send(h2); + if(ret < 0) { + kr_log_verbose("[http] nghttp2_session_send failed: %s\n", nghttp2_strerror(ret)); + return kr_error(EIO); + } + + return 0; +} + +/* + * Send HTTP/2 stream data created from packet's wire buffer. + */ +static int http_write_pkt(nghttp2_session *h2, knot_pkt_t *pkt, int32_t stream_id, + uv_write_t *req, uv_write_cb on_write) +{ + struct http_data *data; + nghttp2_data_provider prov; + const bool is_negative = kr_response_classify(pkt) & (PKT_NODATA|PKT_NXDOMAIN); + + data = malloc(sizeof(struct http_data)); + if (!data) + return kr_error(ENOMEM); + + data->buf = pkt->wire; + data->len = pkt->size; + data->pos = 0; + data->on_write = on_write; + data->req = req; + data->ttl = packet_ttl(pkt, is_negative); + + prov.source.ptr = data; + prov.read_callback = read_callback; + + return http_send_response(h2, stream_id, &prov); +} + +/* + * Write request to HTTP/2 stream. + * + * Packet wire buffer must stay valid until the on_write callback. + */ +int http_write(uv_write_t *req, uv_handle_t *handle, knot_pkt_t *pkt, int32_t stream_id, + uv_write_cb on_write) +{ + struct session *session; + struct http_ctx *ctx; + int ret; + + if (!req || !pkt || !handle || !handle->data || stream_id < 0) + return kr_error(EINVAL); + req->handle = (uv_stream_t *)handle; + + session = handle->data; + if (session_flags(session)->outgoing) + return kr_error(ENOSYS); + + ctx = session_http_get_server_ctx(session); + if (!ctx || !ctx->h2) + return kr_error(EINVAL); + + ret = http_write_pkt(ctx->h2, pkt, stream_id, req, on_write); + if (ret < 0) + return ret; + + return kr_ok(); +} + +/* + * Release HTTP/2 context. + */ +void http_free(struct http_ctx *ctx) +{ + if (!ctx) + return; + + queue_deinit(ctx->streams); + nghttp2_session_del(ctx->h2); + free(ctx); +} diff -Nru knot-resolver-5.1.1/daemon/http.h knot-resolver-5.2.1/daemon/http.h --- knot-resolver-5.1.1/daemon/http.h 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/http.h 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 CZ.NIC, z.s.p.o + * + * Initial Author: Jan Hák + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include + +#if ENABLE_DOH2 +#include +#endif + +#include "lib/generic/queue.h" + +/** Transport session (opaque). */ +struct session; + +typedef ssize_t(*http_send_callback)(const uint8_t *buffer, + const size_t buffer_len, + struct session *session); + +typedef queue_t(int32_t) queue_int32_t; + +typedef enum { + HTTP_METHOD_NONE = 0, + HTTP_METHOD_GET = 1, + HTTP_METHOD_POST = 2, +} http_method_t; + +struct http_ctx { + struct nghttp2_session *h2; + http_send_callback send_cb; + struct session *session; + queue_int32_t streams; /* IDs of streams present in the buffer. */ + int32_t incomplete_stream; + ssize_t submitted; + http_method_t current_method; + char *uri_path; + uint8_t *buf; /* Part of the wire_buf that belongs to current HTTP/2 stream. */ + ssize_t buf_pos; + ssize_t buf_size; +}; + +#if ENABLE_DOH2 +struct http_ctx* http_new(struct session *session, http_send_callback send_cb); +ssize_t http_process_input_data(struct session *session, const uint8_t *buf, ssize_t nread); +int http_write(uv_write_t *req, uv_handle_t *handle, knot_pkt_t* pkt, int32_t stream_id, + uv_write_cb on_write); +void http_free(struct http_ctx *ctx); +#endif diff -Nru knot-resolver-5.1.1/daemon/io.c knot-resolver-5.2.1/daemon/io.c --- knot-resolver-5.1.1/daemon/io.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/io.c 2020-12-09 09:44:29.000000000 +0000 @@ -1,18 +1,28 @@ -/* Copyright (C) 2014-2017 CZ.NIC, z.s.p.o. +/* Copyright (C) 2014-2020 CZ.NIC, z.s.p.o. * SPDX-License-Identifier: GPL-3.0-or-later */ -#include -#include +#include "daemon/io.h" + +#include #include #include -#include +#include +#include +#include + +#if ENABLE_XDP + #include + #include +#endif -#include "daemon/io.h" #include "daemon/network.h" #include "daemon/worker.h" #include "daemon/tls.h" +#include "daemon/http.h" #include "daemon/session.h" +#include "contrib/cleanup.h" +#include "lib/utils.h" #define negotiate_bufsize(func, handle, bufsize_want) do { \ int bufsize = 0; (func)((handle), &bufsize); \ @@ -60,21 +70,10 @@ void udp_recv(uv_udp_t *handle, ssize_t nread, const uv_buf_t *buf, const struct sockaddr *addr, unsigned flags) { - uv_loop_t *loop = handle->loop; - struct worker_ctx *worker = loop->data; struct session *s = handle->data; - if (session_flags(s)->closing) { + if (session_flags(s)->closing || nread <= 0 || addr->sa_family == AF_UNSPEC) return; - } - if (nread <= 0) { - if (nread < 0) { /* Error response, notify resolver */ - worker_submit(s, NULL, NULL); - } /* nread == 0 is for freeing buffers, we don't need to do this */ - return; - } - if (addr->sa_family == AF_UNSPEC) { - return; - } + if (session_flags(s)->outgoing) { const struct sockaddr *peer = session_get_peer(s); assert(peer->sa_family != AF_UNSPEC); @@ -89,7 +88,7 @@ assert(consumed == nread); (void)consumed; session_wirebuf_process(s, addr); session_wirebuf_discard(s); - mp_flush(worker->pkt_pool.ctx); + mp_flush(the_worker->pkt_pool.ctx); } static int family_to_freebind_option(sa_family_t sa_family, int *level, int *name) @@ -153,6 +152,23 @@ if (setsockopt(fd, optlevel, optname, &yes, sizeof(yes))) return kr_error(errno); } + + /* Linux 3.15 has IP_PMTUDISC_OMIT which makes sockets + * ignore PMTU information and send packets with DF=0. + * This mitigates DNS fragmentation attacks by preventing + * forged PMTU information. FreeBSD already has same semantics + * without setting the option. + https://gitlab.nic.cz/knot/knot-dns/-/issues/640 + */ +#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_OMIT) + int omit = IP_PMTUDISC_OMIT; + if (type == SOCK_DGRAM && addr->sa_family == AF_INET + && setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &omit, sizeof(omit))) { + kr_log_error( + "[ io ] failed to disable Path MTU discovery for %s UDP: %s\n", + kr_straddr(addr), strerror(errno)); + } +#endif } if (bind(fd, addr, kr_sockaddr_len(addr))) @@ -175,7 +191,7 @@ uv_handle_t *h = (uv_handle_t *)handle; check_bufsize(h); /* Handle is already created, just create context. */ - struct session *s = session_new(h, false); + struct session *s = session_new(h, false, false); assert(s); session_flags(s)->outgoing = false; @@ -195,11 +211,9 @@ assert(!session_flags(s)->closing); - struct worker_ctx *worker = timer->loop->data; - if (!session_tasklist_is_empty(s)) { int finalized = session_tasklist_finalize_expired(s); - worker->stats.timeout += finalized; + the_worker->stats.timeout += finalized; /* session_tasklist_finalize_expired() may call worker_task_finalize(). * If session is a source session and there were IO errors, * worker_task_finalize() can filnalize all tasks and close session. */ @@ -220,13 +234,12 @@ struct qr_task *t = session_waitinglist_pop(s, false); worker_task_finalize(t, KR_STATE_FAIL); worker_task_unref(t); - worker->stats.timeout += 1; + the_worker->stats.timeout += 1; if (session_flags(s)->closing) { return; } } - const struct engine *engine = worker->engine; - const struct network *net = &engine->net; + const struct network *net = &the_worker->engine->net; uint64_t idle_in_timeout = net->tcp.in_idle_timeout; uint64_t last_activity = session_last_activity(s); uint64_t idle_time = kr_now() - last_activity; @@ -241,8 +254,8 @@ kr_log_verbose("[io] => closing connection to '%s'\n", peer_str ? peer_str : ""); if (session_flags(s)->outgoing) { - worker_del_tcp_waiting(worker, peer); - worker_del_tcp_connected(worker, peer); + worker_del_tcp_waiting(the_worker, peer); + worker_del_tcp_connected(the_worker, peer); } session_close(s); } @@ -300,6 +313,26 @@ data = session_wirebuf_get_free_start(s); data_len = consumed; } +#if ENABLE_DOH2 + if (session_flags(s)->has_http) { + consumed = http_process_input_data(s, data, data_len); + if (consumed < 0) { + if (kr_verbose_status) { + struct sockaddr *peer = session_get_peer(s); + char *peer_str = kr_straddr(peer); + kr_log_verbose("[io] => connection to '%s': " + "error processing HTTP data, close\n", + peer_str ? peer_str : ""); + } + worker_end_tcp(s); + return; + } else if (consumed == 0) { + return; + } + data = session_wirebuf_get_free_start(s); + data_len = consumed; + } +#endif /* data points to start of the free space in session wire buffer. Simple increase internal counter. */ @@ -312,11 +345,27 @@ worker_end_tcp(s); } session_wirebuf_compress(s); - struct worker_ctx *worker = handle->loop->data; - mp_flush(worker->pkt_pool.ctx); + mp_flush(the_worker->pkt_pool.ctx); +} + +#if ENABLE_DOH2 +static ssize_t tls_send(const uint8_t *buf, const size_t len, struct session *session) +{ + struct tls_ctx *ctx = session_tls_get_server_ctx(session); + ssize_t sent = 0; + assert(ctx); + + sent = gnutls_record_send(ctx->c.tls_session, buf, len); + if (sent < 0) { + kr_log_verbose("[http] gnutls_record_send failed: %s (%zd)\n", + gnutls_strerror_name(sent), sent); + return kr_error(EIO); + } + return sent; } +#endif -static void _tcp_accept(uv_stream_t *master, int status, bool tls) +static void _tcp_accept(uv_stream_t *master, int status, bool tls, bool http) { if (status != 0) { return; @@ -328,7 +377,7 @@ return; } int res = io_create(master->loop, (uv_handle_t *)client, - SOCK_STREAM, AF_UNSPEC, tls); + SOCK_STREAM, AF_UNSPEC, tls, http); if (res) { if (res == UV_EMFILE) { worker->too_many_open = true; @@ -380,7 +429,7 @@ uint64_t timeout = KR_CONN_RTT_MAX / 2; if (tls) { timeout += TLS_MAX_HANDSHAKE_TIME; - struct tls_ctx_t *ctx = session_tls_get_server_ctx(s); + struct tls_ctx *ctx = session_tls_get_server_ctx(s); if (!ctx) { ctx = tls_new(worker); if (!ctx) { @@ -389,32 +438,94 @@ } ctx->c.session = s; ctx->c.handshake_state = TLS_HS_IN_PROGRESS; + + /* Configure ALPN. */ + gnutls_datum_t proto; + if (!http) { + proto.data = (unsigned char *)"dot"; + proto.size = 3; + } else { + proto.data = (unsigned char *)"h2"; + proto.size = 2; + } + unsigned int flags = 0; +#if GNUTLS_VERSION_NUMBER >= 0x030500 + /* Mandatory ALPN means the protocol must match if and + * only if ALPN extension is used by the client. */ + flags |= GNUTLS_ALPN_MANDATORY; +#endif + ret = gnutls_alpn_set_protocols(ctx->c.tls_session, &proto, 1, flags); + if (ret != GNUTLS_E_SUCCESS) { + session_close(s); + return; + } + session_tls_set_server_ctx(s, ctx); } } +#if ENABLE_DOH2 + if (http) { + struct http_ctx *ctx = session_http_get_server_ctx(s); + if (!ctx) { + if (!tls) { /* Plain HTTP is not supported. */ + session_close(s); + return; + } + ctx = http_new(s, tls_send); + if (!ctx) { + session_close(s); + return; + } + session_http_set_server_ctx(s, ctx); + } + } +#endif session_timer_start(s, tcp_timeout_trigger, timeout, idle_in_timeout); io_start_read((uv_handle_t *)client); } static void tcp_accept(uv_stream_t *master, int status) { - _tcp_accept(master, status, false); + _tcp_accept(master, status, false, false); } static void tls_accept(uv_stream_t *master, int status) { - _tcp_accept(master, status, true); + _tcp_accept(master, status, true, false); } -int io_listen_tcp(uv_loop_t *loop, uv_tcp_t *handle, int fd, int tcp_backlog, bool has_tls) +#if ENABLE_DOH2 +static void https_accept(uv_stream_t *master, int status) { - const uv_connection_cb connection = has_tls ? tls_accept : tcp_accept; + _tcp_accept(master, status, true, true); +} +#endif + +int io_listen_tcp(uv_loop_t *loop, uv_tcp_t *handle, int fd, int tcp_backlog, bool has_tls, bool has_http) +{ + uv_connection_cb connection; + if (!handle) { return kr_error(EINVAL); } int ret = uv_tcp_init(loop, handle); if (ret) return ret; + if (has_tls && has_http) { +#if ENABLE_DOH2 + connection = https_accept; +#else + kr_log_error("[ io ] kresd was compiled without libnghttp2 support\n"); + return kr_error(ENOPROTOOPT); +#endif + } else if (has_tls) { + connection = tls_accept; + } else if (has_http) { + return kr_error(EPROTONOSUPPORT); + } else { + connection = tcp_accept; + } + ret = uv_tcp_open(handle, (uv_os_sock_t) fd); if (ret) return ret; @@ -450,6 +561,19 @@ return 0; } + +enum io_stream_mode { + io_mode_text = 0, + io_mode_binary = 1, +}; + +struct io_stream_data { + enum io_stream_mode mode; + size_t blen; ///< length of `buf` + char *buf; ///< growing buffer residing on `pool` (mp_append_*) + knot_mm_t *pool; +}; + /** * TTY control: process input and free() the buffer. * @@ -459,114 +583,145 @@ */ void io_tty_process_input(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { - char *commands = buf ? buf->base : NULL; /* To be free()d on return. */ + auto_free char *commands = buf ? buf->base : NULL; /* Set output streams */ FILE *out = stdout; - uv_os_fd_t stream_fd = 0; + uv_os_fd_t stream_fd = -1; struct args *args = the_args; - if (uv_fileno((uv_handle_t *)stream, &stream_fd)) { + struct io_stream_data *data = (struct io_stream_data*) stream->data; + if (nread < 0 || uv_fileno((uv_handle_t *)stream, &stream_fd)) { + mp_delete(data->pool->ctx); uv_close((uv_handle_t *)stream, (uv_close_cb) free); - free(commands); + return; + } + if (nread <= 0) { return; } if (stream_fd != STDIN_FILENO) { - if (nread < 0) { /* Close if disconnected */ - uv_close((uv_handle_t *)stream, (uv_close_cb) free); - } - if (nread <= 0) { - free(commands); - return; - } uv_os_fd_t dup_fd = dup(stream_fd); if (dup_fd >= 0) { out = fdopen(dup_fd, "w"); } } - char *cmd = NULL; - /* Execute */ - if (stream && commands && nread > 0) { - /* Ensure commands is 0-terminated */ - if (commands[nread - 1] == '\n') { - commands[nread - 1] = '\0'; - } else { - if (nread >= buf->len) { /* only equality should be possible */ - char *newbuf = realloc(commands, nread + 1); - if (!newbuf) - goto finish; - commands = newbuf; - } - commands[nread] = '\0'; - } + /** The current single command and the remaining command(s). */ + char *cmd, *cmd_next = NULL; + bool incomplete_cmd = false; - const char *delim = args->quiet ? "" : "> "; + if (!(stream && commands && nread > 0)) { + goto finish; + } + /* Execute */ - /* No command, just new line */ - if (nread == 1 && args->tty_binary_output == false && commands[nread-1] == '\0') { - if (stream_fd != STDIN_FILENO) { - fprintf(out, "%s", delim); - } - if (stream_fd == STDIN_FILENO || VERBOSE_STATUS) { - fprintf(stdout, "%s", delim); - } - } + if (commands[nread - 1] != '\n') { + incomplete_cmd = true; + } + /* Ensure commands is 0-terminated */ + if (nread >= buf->len) { /* only equality should be possible */ + char *newbuf = realloc(commands, nread + 1); + if (!newbuf) + goto finish; + commands = newbuf; + } + commands[nread] = '\0'; + + char *boundary = "\n\0"; + cmd = strtok(commands, "\n"); + /* strtok skip '\n' but we need process alone '\n' too */ + if (commands[0] == '\n') { + cmd_next = cmd; + cmd = boundary; + } else { + cmd_next = strtok(NULL, "\n"); + } - cmd = strtok(commands, "\n"); - while (cmd != NULL) { - /* Pseudo-command for switching to "binary output"; */ - if (strcmp(cmd, "__binary") == 0) { - args->tty_binary_output = true; - cmd = strtok(NULL, "\n"); - continue; - } - - lua_State *L = the_worker->engine->L; - int ret = engine_cmd(L, cmd, false); - const char *message = ""; - if (lua_gettop(L) > 0) { - message = lua_tostring(L, -1); - } - - /* Simpler output in binary mode */ - if (args->tty_binary_output) { - size_t len_s = strlen(message); - if (len_s > UINT32_MAX) { - cmd = strtok(NULL, "\n"); - continue; - } - uint32_t len_n = htonl(len_s); - fwrite(&len_n, sizeof(len_n), 1, out); + /** Moving pointer to end of buffer with incomplete command. */ + char *pbuf = data->buf + data->blen; + lua_State *L = the_worker->engine->L; + while (cmd != NULL) { + /* Last command is incomplete - save it and execute later */ + if (incomplete_cmd && cmd_next == NULL) { + pbuf = mp_append_string(data->pool->ctx, pbuf, cmd); + mp_append_char(data->pool->ctx, pbuf, '\0'); + data->buf = mp_ptr(data->pool->ctx); + data->blen = data->blen + strlen(cmd); + + /* There is new incomplete command */ + if (commands[nread - 1] == '\n') + incomplete_cmd = false; + goto next_iter; + } + + /* Process incomplete command from previously call */ + if (data->blen > 0) { + if (commands[0] != '\n' && commands[0] != '\0') { + pbuf = mp_append_string(data->pool->ctx, pbuf, cmd); + mp_append_char(data->pool->ctx, pbuf, '\0'); + data->buf = mp_ptr(data->pool->ctx); + cmd = data->buf; + } else { + cmd = data->buf; + } + data->blen = 0; + pbuf = data->buf; + } + + /* Pseudo-command for switching to "binary output"; */ + if (strcmp(cmd, "__binary") == 0) { + data->mode = io_mode_binary; + goto next_iter; + } + + int ret = engine_cmd(L, cmd, false); + const char *message = NULL; + size_t len_s; + if (lua_gettop(L) > 0) { + message = lua_tolstring(L, -1, &len_s); + } + + /* Simpler output in binary mode */ + if (data->mode == io_mode_binary) { + /* Leader expects length field in all cases */ + if (!message || len_s > UINT32_MAX) { + kr_log_error("[io] unrepresentable respose on control socket, " + "sending back empty block (command '%s')\n", cmd); + len_s = 0; + } + uint32_t len_n = htonl(len_s); + fwrite(&len_n, sizeof(len_n), 1, out); + if (len_s > 0) fwrite(message, len_s, 1, out); - lua_settop(L, 0); - cmd = strtok(NULL, "\n"); - continue; - } - /* Log to remote socket if connected */ - if (stream_fd != STDIN_FILENO) { - if (VERBOSE_STATUS) - fprintf(stdout, "%s\n", cmd); /* Duplicate command to logs */ - if (message) - fprintf(out, "%s", message); /* Duplicate output to sender */ - if (message || !args->quiet) - fprintf(out, "\n"); - fprintf(out, "%s", delim); - } - if (stream_fd == STDIN_FILENO || VERBOSE_STATUS) { - /* Log to standard streams */ - FILE *fp_out = ret ? stderr : stdout; - if (message) - fprintf(fp_out, "%s", message); - if (message || !args->quiet) - fprintf(fp_out, "\n"); - fprintf(fp_out, "%s", delim); - } - lua_settop(L, 0); - cmd = strtok(NULL, "\n"); + goto next_iter; } + + /* Log to remote socket if connected */ + const char *delim = args->quiet ? "" : "> "; + if (stream_fd != STDIN_FILENO) { + if (VERBOSE_STATUS) + fprintf(stdout, "%s\n", cmd); /* Duplicate command to logs */ + if (message) + fprintf(out, "%s", message); /* Duplicate output to sender */ + if (message || !args->quiet) + fprintf(out, "\n"); + fprintf(out, "%s", delim); + } + if (stream_fd == STDIN_FILENO || VERBOSE_STATUS) { + /* Log to standard streams */ + FILE *fp_out = ret ? stderr : stdout; + if (message) + fprintf(fp_out, "%s", message); + if (message && !args->quiet) + fprintf(fp_out, "\n"); + fprintf(fp_out, "%s", delim); + } + next_iter: + lua_settop(L, 0); /* not required in some cases but harmless */ + cmd = cmd_next; + cmd_next = strtok(NULL, "\n"); } + finish: - free(commands); /* Close if redirected */ if (stream_fd != STDIN_FILENO) { fclose(out); @@ -579,14 +734,39 @@ buf->base = malloc(suggested); } +struct io_stream_data *io_tty_alloc_data() { + knot_mm_t _pool = { + .ctx = mp_new(4096), + .alloc = (knot_mm_alloc_t) mp_alloc, + }; + knot_mm_t *pool = mm_alloc(&_pool, sizeof(*pool)); + if (!pool) { + return NULL; + } + memcpy(pool, &_pool, sizeof(*pool)); + + struct io_stream_data *data = mm_alloc(pool, sizeof(struct io_stream_data)); + + data->buf = mp_start(pool->ctx, 512); + data->mode = io_mode_text; + data->blen = 0; + data->pool = pool; + + return data; +} + void io_tty_accept(uv_stream_t *master, int status) { - uv_tcp_t *client = malloc(sizeof(*client)); + struct io_stream_data *data = io_tty_alloc_data(); + /* We can't use any allocations after mp_start() and it's easier anyway. */ + uv_pipe_t *client = malloc(sizeof(*client)); + client->data = data; + struct args *args = the_args; - if (client) { - uv_tcp_init(master->loop, client); + if (client && client->data) { + uv_pipe_init(master->loop, client, 0); if (uv_accept(master, (uv_stream_t *)client) != 0) { - free(client); + mp_delete(data->pool->ctx); return; } uv_read_start((uv_stream_t *)client, io_tty_alloc, io_tty_process_input); @@ -617,7 +797,130 @@ return 0; } -int io_create(uv_loop_t *loop, uv_handle_t *handle, int type, unsigned family, bool has_tls) +#if ENABLE_XDP +static void xdp_rx(uv_poll_t* handle, int status, int events) +{ + const int XDP_RX_BATCH_SIZE = 64; + if (status < 0) { + kr_log_error("[xdp] poll status %d: %s\n", status, uv_strerror(status)); + return; + } + if (events != UV_READABLE) { + kr_log_error("[xdp] poll unexpected events: %d\n", events); + return; + } + + xdp_handle_data_t *xhd = handle->data; + assert(xhd && xhd->session && xhd->socket); + uint32_t rcvd; + knot_xdp_msg_t msgs[XDP_RX_BATCH_SIZE]; + int ret = knot_xdp_recv(xhd->socket, msgs, XDP_RX_BATCH_SIZE, &rcvd); + if (ret == KNOT_EOK) { + kr_log_verbose("[xdp] poll triggered, processing a batch of %d packets\n", + (int)rcvd); + } else { + kr_log_error("[xdp] knot_xdp_recv(): %d, %s\n", ret, knot_strerror(ret)); + assert(false); // ATM it can only be returned when called incorrectly + return; + } + assert(rcvd <= XDP_RX_BATCH_SIZE); + for (int i = 0; i < rcvd; ++i) { + const knot_xdp_msg_t *msg = &msgs[i]; + assert(msg->payload.iov_len <= KNOT_WIRE_MAX_PKTSIZE); + knot_pkt_t *kpkt = knot_pkt_new(msg->payload.iov_base, msg->payload.iov_len, + &the_worker->pkt_pool); + if (kpkt == NULL) { + ret = kr_error(ENOMEM); + } else { + ret = worker_submit(xhd->session, + (const struct sockaddr *)&msg->ip_from, + (const struct sockaddr *)&msg->ip_to, + msg->eth_from, msg->eth_to, kpkt); + } + if (ret) + kr_log_verbose("[xdp] worker_submit() == %d: %s\n", ret, kr_strerror(ret)); + mp_flush(the_worker->pkt_pool.ctx); + } + knot_xdp_recv_finish(xhd->socket, msgs, rcvd); +} +/// Warn if the XDP program is running in emulated mode (XDP_SKB) +static void xdp_warn_mode(const char *ifname) +{ + assert(ifname); + const unsigned if_index = if_nametoindex(ifname); + if (!if_index) { + kr_log_info("[xdp] warning: interface %s, unexpected error when converting its name: %s\n", + ifname, strerror(errno)); + return; + } + + const knot_xdp_mode_t mode = knot_eth_xdp_mode(if_index); + switch (mode) { + case KNOT_XDP_MODE_FULL: + return; + case KNOT_XDP_MODE_EMUL: + kr_log_info("[xdp] warning: interface %s running only with XDP emulation\n", + ifname); + return; + case KNOT_XDP_MODE_NONE: // enum warnings from compiler + break; + } + kr_log_info("[xdp] warning: interface %s running in unexpected XDP mode %d\n", + ifname, (int)mode); +} +int io_listen_xdp(uv_loop_t *loop, struct endpoint *ep, const char *ifname) +{ + if (!ep || !ep->handle) { + return kr_error(EINVAL); + } + + // RLIMIT_MEMLOCK often needs raising when operating on BPF + static int ret_limit = 1; + if (ret_limit == 1) { + struct rlimit no_limit = { RLIM_INFINITY, RLIM_INFINITY }; + ret_limit = setrlimit(RLIMIT_MEMLOCK, &no_limit) + ? kr_error(errno) : 0; + } + if (ret_limit) return ret_limit; + + xdp_handle_data_t *xhd = malloc(sizeof(*xhd)); + if (!xhd) return kr_error(ENOMEM); + + const int port = ep->port ? ep->port : KNOT_XDP_LISTEN_PORT_ALL; + xhd->socket = NULL; // needed for some reason + int ret = knot_xdp_init(&xhd->socket, ifname, ep->nic_queue, port, + KNOT_XDP_LOAD_BPF_MAYBE); + if (!ret) xdp_warn_mode(ifname); + + if (!ret) ret = uv_idle_init(loop, &xhd->tx_waker); + if (ret) { + free(xhd); + return kr_error(ret); + } + assert(xhd->socket); + xhd->tx_waker.data = xhd->socket; + + ep->fd = knot_xdp_socket_fd(xhd->socket); // probably not useful + ret = uv_poll_init(loop, (uv_poll_t *)ep->handle, ep->fd); + if (ret) { + knot_xdp_deinit(xhd->socket); + free(xhd); + return kr_error(ret); + } + + // beware: this sets poll_handle->data + xhd->session = session_new(ep->handle, false, false); + assert(!session_flags(xhd->session)->outgoing); + session_get_sockname(xhd->session)->sa_family = AF_XDP; // to have something in there + + ep->handle->data = xhd; + ret = uv_poll_start((uv_poll_t *)ep->handle, UV_READABLE, xdp_rx); + return ret; +} +#endif + + +int io_create(uv_loop_t *loop, uv_handle_t *handle, int type, unsigned family, bool has_tls, bool has_http) { int ret = -1; if (type == SOCK_DGRAM) { @@ -629,21 +932,31 @@ if (ret != 0) { return ret; } - struct session *s = session_new(handle, has_tls); + struct session *s = session_new(handle, has_tls, has_http); if (s == NULL) { ret = -1; } return ret; } -void io_deinit(uv_handle_t *handle) +static void io_deinit(uv_handle_t *handle) { - if (!handle) { + if (!handle || !handle->data) { return; } - if (handle->data) { + if (handle->type != UV_POLL) { session_free(handle->data); - handle->data = NULL; + } else { + #if ENABLE_XDP + xdp_handle_data_t *xhd = handle->data; + uv_idle_stop(&xhd->tx_waker); + uv_close((uv_handle_t *)&xhd->tx_waker, NULL); + session_free(xhd->session); + knot_xdp_deinit(xhd->socket); + free(xhd); + #else + assert(false); + #endif } } diff -Nru knot-resolver-5.1.1/daemon/io.h knot-resolver-5.2.1/daemon/io.h --- knot-resolver-5.1.1/daemon/io.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/io.h 2020-12-09 09:44:29.000000000 +0000 @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2017 CZ.NIC, z.s.p.o. +/* Copyright (C) 2014-2020 CZ.NIC, z.s.p.o. * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -12,22 +12,27 @@ #include "daemon/worker.h" #include "daemon/engine.h" -struct tls_ctx_t; -struct tls_client_ctx_t; +struct tls_ctx; +struct tls_client_ctx; +struct io_stream_data; /** Bind address into a file-descriptor (only, no libuv). type is e.g. SOCK_DGRAM */ int io_bind(const struct sockaddr *addr, int type, const endpoint_flags_t *flags); /** Initialize a UDP handle and start listening. */ int io_listen_udp(uv_loop_t *loop, uv_udp_t *handle, int fd); /** Initialize a TCP handle and start listening. */ -int io_listen_tcp(uv_loop_t *loop, uv_tcp_t *handle, int fd, int tcp_backlog, bool has_tls); +int io_listen_tcp(uv_loop_t *loop, uv_tcp_t *handle, int fd, int tcp_backlog, bool has_tls, bool has_http); /** Initialize a pipe handle and start listening. */ int io_listen_pipe(uv_loop_t *loop, uv_pipe_t *handle, int fd); +/** Initialize a poll handle (ep->handle) and start listening over AF_XDP on ifname. + * Sets ep->session. */ +int io_listen_xdp(uv_loop_t *loop, struct endpoint *ep, const char *ifname); /** Control socket / TTY - related functions. */ void io_tty_process_input(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf); void io_tty_alloc(uv_handle_t *handle, size_t suggested, uv_buf_t *buf); void io_tty_accept(uv_stream_t *master, int status); +struct io_stream_data *io_tty_alloc_data(void); void tcp_timeout_trigger(uv_timer_t *timer); @@ -36,9 +41,17 @@ * \param family = AF_* * \param has_tls has meanings only when type is SOCK_STREAM */ int io_create(uv_loop_t *loop, uv_handle_t *handle, int type, - unsigned family, bool has_tls); -void io_deinit(uv_handle_t *handle); + unsigned family, bool has_tls, bool has_http); void io_free(uv_handle_t *handle); int io_start_read(uv_handle_t *handle); int io_stop_read(uv_handle_t *handle); + +/** When uv_handle_t::type == UV_POLL, ::data points to this malloc-ed helper. + * (Other cases store a direct struct session pointer in ::data.) */ +typedef struct { + struct knot_xdp_socket *socket; + struct session *session; + uv_idle_t tx_waker; +} xdp_handle_data_t; + diff -Nru knot-resolver-5.1.1/daemon/lua/controlsock.test.lua knot-resolver-5.2.1/daemon/lua/controlsock.test.lua --- knot-resolver-5.1.1/daemon/lua/controlsock.test.lua 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/lua/controlsock.test.lua 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,169 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +local cqsocket = require('cqueues.socket') +local strerror = require('cqueues.errno').strerror +local timeout = 5 -- seconds, per socket operation + +-- TODO: we get memory leaks from cqueues, but CI runs this without leak detection anyway + +local ctrl_sock_txt, ctrl_sock_bin, ctrl_sock_txt_longcmd, ctrl_sock_bin_longcmd +local ctrl_sock_txt_partcmd, ctrl_sock_bin_partcmd + +local function onerr_fail(_, method, errno, stacklevel) + local errmsg = string.format('socket error: method %s error %d (%s)', + method, errno, strerror(errno)) + fail(debug.traceback(errmsg, stacklevel)) +end + + +local function switch_to_binary_mode(sock) + data = sock:xread(2, nil, timeout) + sock:xwrite('__binary\n', nil, timeout) + same(data, '> ', 'propably successsful switch to binary mode') +end + +local function socket_connect(path) + sock = cqsocket.connect({ path = path, nonblock = true }) + sock:onerror(onerr_fail) + sock:setmode('bn', 'bn') + + return sock +end + +local function socket_fixture() + local path = worker.cwd..'/control/'..worker.pid + same(true, net.listen(path, nil, {kind = 'control'}), 'new control sockets were created') + + ctrl_sock_txt = socket_connect(path) + ctrl_sock_txt_longcmd = socket_connect(path) + ctrl_sock_txt_partcmd = socket_connect(path) + + ctrl_sock_bin = socket_connect(path) + switch_to_binary_mode(ctrl_sock_bin) + ctrl_sock_bin_longcmd = socket_connect(path) + switch_to_binary_mode(ctrl_sock_bin_longcmd) + ctrl_sock_bin_partcmd = socket_connect(path) + switch_to_binary_mode(ctrl_sock_bin_partcmd) +end + +local function test_text_prompt() + data = ctrl_sock_txt:xread(2, nil, timeout) + same(data, '> ', 'text prompt looks like expected') +end + +local function test_text_single_command() + local string = "this is test" + local input = string.format("'%s'\n", string) + local expect = input + ctrl_sock_txt:xwrite(input, nil, timeout) + data = ctrl_sock_txt:xread(#expect, nil, timeout) + same(data, expect, + 'text mode returns output in expected format') +end + +local function binary_xread_len(sock) + data = sock:xread(4, nil, timeout) + local len = tonumber(data:byte(1)) + for i=2,4 do + len = bit.bor(bit.lshift(len, 8), tonumber(data:byte(i))) + end + + return len +end + +local function test_binary_more_syscalls() + local len + + ctrl_sock_bin:xwrite('worker.p', nil, timeout) + worker.sleep(0.01) + ctrl_sock_bin:xwrite('id\n', nil, timeout) + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, tostring(worker.pid), + 'binary mode returns number in expected format') + + ctrl_sock_bin:xwrite('worker.p', nil, timeout) + worker.sleep(0.01) + ctrl_sock_bin:xwrite('id\nworker.id\n', nil, timeout) + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, tostring(worker.pid), + 'binary mode returns number in expected format') + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, string.format("'%s'", worker.id), + 'binary mode returns string in expected format') + + ctrl_sock_bin:xwrite('worker.pid', nil, timeout) + worker.sleep(0.01) + ctrl_sock_bin:xwrite('\n', nil, timeout) + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, tostring(worker.pid), + 'binary mode returns output in expected format') + + ctrl_sock_bin:xwrite('worker.pid', nil, timeout) + worker.sleep(0.01) + ctrl_sock_bin:xwrite('\nworker.id', nil, timeout) + worker.sleep(0.01) + ctrl_sock_bin:xwrite('\n', nil, timeout) + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, tostring(worker.pid), + 'binary mode returns number in expected format') + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, string.format("'%s'", worker.id), + 'binary mode returns string in expected format') + + ctrl_sock_bin:xwrite('worker.pid\nworker.pid\nworker.pid\nworker.pid\n', nil, timeout) + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, tostring(worker.pid), + 'binary mode returns number in expected format') + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, tostring(worker.pid), + 'binary mode returns number in expected format') + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, tostring(worker.pid), + 'binary mode returns number in expected format') + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, tostring(worker.pid), + 'binary mode returns number in expected format') +end + +local function test_close_incomplete_cmd() + ctrl_sock_txt_partcmd:xwrite('worker.p', nil, timeout) + ctrl_sock_txt_partcmd:close() + pass('close text socket with short incomplete command') + + ctrl_sock_bin_partcmd:xwrite('worker.p', nil, timeout) + ctrl_sock_bin_partcmd:close() + pass('close binary socket with short incomplete command') +end + + +local function test_close_during_transfer() + ctrl_sock_txt_longcmd:xwrite(string.rep('a', 1024*1024*10), nil, timeout) + ctrl_sock_txt_longcmd:close() + pass('close text socket with long incomplete command') + + ctrl_sock_bin_longcmd:xwrite(string.rep('a', 1024*1024*10), nil, timeout) + ctrl_sock_bin_longcmd:close() + pass('close binary socket with long incomplete command') +end + +local tests = { + socket_fixture, + test_text_prompt, -- prompt after connect + test_text_single_command, + test_text_prompt, -- new prompt when command is finished + test_close_incomplete_cmd, + test_close_during_transfer, + test_binary_more_syscalls, + test_text_single_command, -- command in text mode after execute commands in binary mode + test_text_prompt, -- new prompt when command is finished +} +return tests diff -Nru knot-resolver-5.1.1/daemon/lua/distro-preconfig.lua.in knot-resolver-5.2.1/daemon/lua/distro-preconfig.lua.in --- knot-resolver-5.1.1/daemon/lua/distro-preconfig.lua.in 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/lua/distro-preconfig.lua.in 2020-12-09 09:44:29.000000000 +0000 @@ -4,7 +4,8 @@ warn('environment variable $SYSTEMD_INSTANCE not set') else -- Bind to control socket in run_dir - local path = '@run_dir@/control/'..id + worker.control_path = '@run_dir@/control/' + local path = worker.control_path..id local ok, err = pcall(net.listen, path, nil, { kind = 'control' }) if not ok then warn('bind to '..path..' failed '..err) diff -Nru knot-resolver-5.1.1/daemon/lua/kluautil.lua knot-resolver-5.2.1/daemon/lua/kluautil.lua --- knot-resolver-5.1.1/daemon/lua/kluautil.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/lua/kluautil.lua 2020-12-09 09:44:29.000000000 +0000 @@ -1,10 +1,13 @@ -- SPDX-License-Identifier: GPL-3.0-or-later -local cqerrno = require('cqueues.errno') local kluautil = {} -- Get length of table function kluautil.kr_table_len(t) + if type(t) ~= 'table' then + return nil + end + local len = 0 for _ in pairs(t) do len = len + 1 @@ -12,6 +15,18 @@ return len end +-- pack varargs including nil arguments into a table +function kluautil.kr_table_pack(...) + local tab = {...} + tab.n = select('#', ...) + return tab +end + +-- unpack table produced by kr_table_pack and including nil values +function kluautil.kr_table_unpack(tab) + return unpack(tab, 1, tab.n) +end + -- Fetch over HTTPS function kluautil.kr_https_fetch(url, out_file, ca_file) local http_ok, http_request = pcall(require, 'http.request') @@ -21,6 +36,7 @@ if not http_ok or not httptls_ok or not openssl_ok then return nil, 'error: lua-http and luaossl libraries are missing (but required)' end + local cqerrno = require('cqueues.errno') assert(string.match(url, '^https://')) @@ -58,9 +74,11 @@ return nil, errmsg end - out_file:seek("set", 0) + out_file:seek('set', 0) return true end +kluautil.list_dir = kluautil_list_dir + return kluautil diff -Nru knot-resolver-5.1.1/daemon/lua/kres-gen.lua knot-resolver-5.2.1/daemon/lua/kres-gen.lua --- knot-resolver-5.1.1/daemon/lua/kres-gen.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/lua/kres-gen.lua 2020-12-09 09:44:29.000000000 +0000 @@ -5,7 +5,6 @@ typedef struct knot_dump_style knot_dump_style_t; extern const knot_dump_style_t KNOT_DUMP_STYLE_DEFAULT; -typedef void knot_db_t; struct kr_cdb_api {}; struct lru {}; @@ -17,6 +16,7 @@ typedef void (*map_free_f)(void *baton, void *ptr); typedef void (*trace_log_f) (const struct kr_request *, const char *); typedef void (*trace_callback_f)(struct kr_request *); +typedef uint8_t * (*alloc_wire_f)(struct kr_request *req, uint16_t *maxlen); typedef enum {KNOT_ANSWER, KNOT_AUTHORITY, KNOT_ADDITIONAL} knot_section_t; typedef struct { uint16_t pos; @@ -161,6 +161,7 @@ _Bool tcp : 1; _Bool tls : 1; _Bool http : 1; + _Bool xdp : 1; }; struct kr_request { struct kr_context *ctx; @@ -172,6 +173,7 @@ const knot_pkt_t *packet; struct kr_request_qsource_flags flags; size_t size; + int32_t stream_id; } qsource; struct { unsigned int rtt; @@ -193,12 +195,15 @@ unsigned int uid; unsigned int count_no_nsaddr; unsigned int count_fail_row; + alloc_wire_f alloc_wire_cb; }; enum kr_rank {KR_RANK_INITIAL, KR_RANK_OMIT, KR_RANK_TRY, KR_RANK_INDET = 4, KR_RANK_BOGUS, KR_RANK_MISMATCH, KR_RANK_MISSING, KR_RANK_INSECURE, KR_RANK_AUTH = 16, KR_RANK_SECURE = 32}; +typedef struct kr_cdb * kr_cdb_pt; struct kr_cdb_stats { uint64_t open; uint64_t close; uint64_t count; + uint64_t count_entries; uint64_t clear; uint64_t commit; uint64_t read; @@ -210,15 +215,18 @@ uint64_t match_miss; uint64_t read_leq; uint64_t read_leq_miss; + double usage_percent; }; +typedef struct uv_timer_s uv_timer_t; struct kr_cache { - knot_db_t *db; + kr_cdb_pt db; const struct kr_cdb_api *api; struct kr_cdb_stats stats; uint32_t ttl_min; uint32_t ttl_max; struct timeval checkpoint_walltime; uint64_t checkpoint_monotime; + uv_timer_t *health_timer; }; typedef struct kr_layer { int state; @@ -291,7 +299,8 @@ }; struct kr_context { struct kr_qflags options; - knot_rrset_t *opt_rr; + knot_rrset_t *downstream_opt_rr; + knot_rrset_t *upstream_opt_rr; map_t trust_anchors; map_t negative_anchors; struct kr_zonecut root_hints; @@ -320,6 +329,7 @@ knot_pkt_t *knot_pkt_new(void *, uint16_t, knot_mm_t *); void knot_pkt_free(knot_pkt_t *); int knot_pkt_parse(knot_pkt_t *, unsigned int); +knot_pkt_t *kr_request_ensure_answer(struct kr_request *); struct kr_rplan *kr_resolve_plan(struct kr_request *); knot_mm_t *kr_resolve_pool(struct kr_request *); struct kr_query *kr_rplan_push(struct kr_rplan *, struct kr_query *, const knot_dname_t *, uint16_t, uint16_t); @@ -355,6 +365,7 @@ int kr_family_len(int); struct sockaddr *kr_straddr_socket(const char *, int, knot_mm_t *); int kr_straddr_split(const char *, char * restrict, uint16_t *); +_Bool kr_rank_test(uint8_t, uint8_t); int kr_ranked_rrarray_add(ranked_rr_array_t *, const knot_rrset_t *, uint8_t, _Bool, uint32_t, knot_mm_t *); int kr_ranked_rrarray_finalize(ranked_rr_array_t *, uint32_t, knot_mm_t *); void kr_qflags_set(struct kr_qflags *, struct kr_qflags); @@ -366,6 +377,7 @@ const char *kr_strptime_diff(const char *, const char *, const char *, double *); time_t kr_file_mtime(const char *); long long kr_fssize(const char *); +const char *kr_dirent_name(const struct dirent *); void lru_free_items_impl(struct lru *); struct lru *lru_create_impl(unsigned int, unsigned int, knot_mm_t *, knot_mm_t *); void *lru_get_impl(struct lru *, const char *, unsigned int, unsigned int, _Bool, _Bool *); @@ -387,8 +399,10 @@ typedef struct { int sock_type; _Bool tls; - const char *kind; + _Bool http; + _Bool xdp; _Bool freebind; + const char *kind; } endpoint_flags_t; typedef struct { char **at; @@ -427,11 +441,14 @@ int fd; int family; uint16_t port; + int16_t nic_queue; _Bool engaged; endpoint_flags_t flags; }; struct request_ctx { struct kr_request req; + struct worker_ctx *worker; + struct qr_task *task; /* beware: hidden stub, to avoid hardcoding sockaddr lengths */ }; struct qr_task { @@ -441,6 +458,15 @@ int worker_resolve_exec(struct qr_task *, knot_pkt_t *); knot_pkt_t *worker_resolve_mk_pkt(const char *, uint16_t, uint16_t, const struct kr_qflags *); struct qr_task *worker_resolve_start(knot_pkt_t *, struct kr_qflags); +struct engine { + struct kr_context resolver; + char _stub[]; +}; +struct worker_ctx { + struct engine *engine; + char _stub[]; +}; +struct worker_ctx *the_worker; typedef struct { uint8_t bitmap[32]; uint8_t length; diff -Nru knot-resolver-5.1.1/daemon/lua/kres-gen.sh knot-resolver-5.2.1/daemon/lua/kres-gen.sh --- knot-resolver-5.1.1/daemon/lua/kres-gen.sh 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/lua/kres-gen.sh 2020-12-09 09:44:29.000000000 +0000 @@ -56,7 +56,6 @@ printf " typedef struct knot_dump_style knot_dump_style_t; extern const knot_dump_style_t KNOT_DUMP_STYLE_DEFAULT; -typedef void knot_db_t; struct kr_cdb_api {}; struct lru {}; " @@ -71,6 +70,7 @@ typedef void (*map_free_f)(void *baton, void *ptr); typedef void (*trace_log_f) (const struct kr_request *, const char *); typedef void (*trace_callback_f)(struct kr_request *); +typedef uint8_t * (*alloc_wire_f)(struct kr_request *req, uint16_t *maxlen); " ${CDEFS} ${LIBKRES} types <<-EOF @@ -114,7 +114,9 @@ struct kr_request_qsource_flags struct kr_request enum kr_rank + typedef kr_cdb_pt struct kr_cdb_stats + typedef uv_timer_t struct kr_cache # lib/layer.h kr_layer_t @@ -178,6 +180,7 @@ ## libkres API ${CDEFS} ${LIBKRES} functions <<-EOF # Resolution request + kr_request_ensure_answer kr_resolve_plan kr_resolve_pool # Resolution plan @@ -216,6 +219,7 @@ kr_family_len kr_straddr_socket kr_straddr_split + kr_rank_test kr_ranked_rrarray_add kr_ranked_rrarray_finalize kr_qflags_set @@ -227,6 +231,7 @@ kr_strptime_diff kr_file_mtime kr_fssize + kr_dirent_name lru_free_items_impl lru_create_impl lru_get_impl @@ -278,6 +283,14 @@ worker_resolve_start EOF +echo "struct engine" | ${CDEFS} ${KRESD} types | sed '/struct network/,$ d' +printf "\tchar _stub[];\n};\n" + +echo "struct worker_ctx" | ${CDEFS} ${KRESD} types | sed '/uv_loop_t/,$ d' +printf "\tchar _stub[];\n};\n" + +echo "struct worker_ctx *the_worker;" + ## libzscanner API for ./zonefile.lua ${CDEFS} libzscanner types <<-EOF diff -Nru knot-resolver-5.1.1/daemon/lua/kres.lua knot-resolver-5.2.1/daemon/lua/kres.lua --- knot-resolver-5.1.1/daemon/lua/kres.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/lua/kres.lua 2020-12-09 09:44:29.000000000 +0000 @@ -455,6 +455,27 @@ C.free(dump[0]) return result end, + txt_fields = function(rr, i) + assert(ffi.istype(knot_rrset_t, rr)) + assert(i >= 0 and i < rr:rdcount()) + local bufsize = 1024 + local dump = ffi.new('char *', C.malloc(bufsize)) + ffi.gc(dump, C.free) + + local ret = knot.knot_rrset_txt_dump_data(rr, i, dump, 1024, + knot.KNOT_DUMP_STYLE_DEFAULT) + if ret >= 0 then + local out = {} + out.owner = dname2str(rr:owner()) + out.ttl = rr:ttl() + out.class = kres.tostring.class[rr:class()] + out.type = kres.tostring.type[rr.type] + out.rdata = ffi.string(dump, ret) + return out + else + panic('knot_rrset_txt_dump_data failure ' .. tostring(ret)) + end + end, -- Return RDATA count for this RR set rdcount = function(rr) assert(ffi.istype(knot_rrset_t, rr)) @@ -893,6 +914,11 @@ req.vars_ref = ref return var end, + -- Ensure that answer exists and return it; can't fail. + ensure_answer = function (req) + assert(ffi.istype(kr_request_t, req)) + return C.kr_request_ensure_answer(req) + end, }, }) @@ -904,11 +930,21 @@ end -- Metatype for a single ranked record array entry (one RRset) +local function rank_tostring(rank) + local names = {} + for name, value in pairs(const_rank) do + if ffi.C.kr_rank_test(rank, value) then + table.insert(names, string.lower(name)) + end + end + return string.format('0%.2o (%s)', rank, table.concat(names, ' ')) +end + local ranked_rr_array_entry_t = ffi.typeof('ranked_rr_array_entry_t') ffi.metatype(ranked_rr_array_entry_t, { __tostring = function(self) - return string.format('; ranked rrset to_wire %s, rank 0%.2o, cached %s, qry_uid %s, revalidations %s\n%s', - self.to_wire, self.rank, self.cached, self.qry_uid, + return string.format('; ranked rrset to_wire %s, rank %s, cached %s, qry_uid %s, revalidations %s\n%s', + self.to_wire, rank_tostring(self.rank), self.cached, self.qry_uid, self.revalidation_cnt, string.format('%s', self.rr)) end }) @@ -1061,7 +1097,7 @@ if ret ~= 1 then return nil end return ffi.string(addr_buf, C.kr_family_len(family)) end, - context = function () return ffi.cast('struct kr_context *', __engine) end, + context = function () return ffi.C.the_worker.engine.resolver end, knot_pkt_rr = knot_pkt_rr, } diff -Nru knot-resolver-5.1.1/daemon/lua/krprint.lua knot-resolver-5.2.1/daemon/lua/krprint.lua --- knot-resolver-5.1.1/daemon/lua/krprint.lua 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/lua/krprint.lua 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,340 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +local base_class = { + cur_indent = 0, +} + +-- shared constructor: use as serializer_class:new() +function base_class.new(class, on_unrepresentable) + on_unrepresentable = on_unrepresentable or 'comment' + if on_unrepresentable ~= 'comment' + and on_unrepresentable ~= 'error' then + error('unsupported val2expr on_unrepresentable option ' + .. tostring(on_unrepresentable)) + end + local inst = {} + inst.on_unrepresentable = on_unrepresentable + inst.done = {} + inst.tab_key_path = {} + setmetatable(inst, class.__inst_mt) + return inst +end + +-- format comment with leading/ending whitespace if needed +function base_class.format_note(_, note, ws_prefix, ws_suffix) + if note == nil then + return '' + else + return string.format('%s--[[ %s ]]%s', + ws_prefix or '', note, ws_suffix or '') + end +end + +function base_class.indent_head(self) + return string.rep(' ', self.cur_indent) +end + +function base_class.indent_inc(self) + self.cur_indent = self.cur_indent + self.indent_step +end + +function base_class.indent_dec(self) + self.cur_indent = self.cur_indent - self.indent_step +end + +function base_class._fallback(self, val) + if self.on_unrepresentable == 'comment' then + return 'nil', string.format('missing %s', val) + elseif self.on_unrepresentable == 'error' then + local key_path_msg + if #self.tab_key_path > 0 then + local str_key_path = {} + for _, key in ipairs(self.tab_key_path) do + table.insert(str_key_path, + string.format('%s %s', type(key), self:string(tostring(key)))) + end + local key_path = '[' .. table.concat(str_key_path, '][') .. ']' + key_path_msg = string.format(' (found at [%s])', key_path) + else + key_path_msg = '' + end + error(string.format('cannot serialize type %s%s', type(val), key_path_msg), 2) + end +end + +function base_class.val2expr(self, val) + local val_type = type(val) + local val_repr = self[val_type] + if val_repr then + return val_repr(self, val) + else + return self:_fallback(val) + end +end + +-- "nil" is a Lua keyword so assignment below is workaround to create +-- function base_class.nil(self, val) +base_class['nil'] = function(_, val) + assert(type(val) == 'nil') + return 'nil' +end + +function base_class.number(_, val) + assert(type(val) == 'number') + if val == math.huge then + return 'math.huge' + elseif val == -math.huge then + return '-math.huge' + elseif tostring(val) == 'nan' then + return 'tonumber(\'nan\')' + else + return string.format("%.60f", val) + end +end + +function base_class.char_is_printable(_, c) + -- ASCII (from space to ~) and not ' or \ + return (c >= 0x20 and c < 0x7f) + and c ~= 0x27 and c ~= 0x5C +end + +function base_class.string(self, val) + assert(type(val) == 'string') + local chars = {'\''} + for i = 1, #val do + local c = string.byte(val, i) + if self:char_is_printable(c) then + table.insert(chars, string.char(c)) + else + table.insert(chars, string.format('\\%03d', c)) + end + end + table.insert(chars, '\'') + return table.concat(chars) +end + +function base_class.boolean(_, val) + assert(type(val) == 'boolean') + return tostring(val) +end + +local function ordered_iter(unordered_tt) + local keys = {} + for k in pairs(unordered_tt) do + table.insert(keys, k) + end + table.sort(keys, + function (a, b) + if type(a) ~= type(b) then + return type(a) < type(b) + end + if type(a) == 'number' then + return a < b + else + return tostring(a) < tostring(b) + end + end) + local i = 0 + return function() + i = i + 1 + if keys[i] ~= nil then + return keys[i], unordered_tt[keys[i]] + end + end +end + +function base_class.table(self, tab) + assert(type(tab) == 'table') + if self.done[tab] then + error('cyclic reference', 0) + end + self.done[tab] = true + + local items = {'{'} + local previdx = 0 + self:indent_inc() + for idx, val in ordered_iter(tab) do + local errors, valok, valexpr, valnote, idxok, idxexpr, idxnote + errors = {} + -- push current index onto key path stack to make it available to sub-printers + table.insert(self.tab_key_path, idx) + + valok, valexpr, valnote = pcall(self.val2expr, self, val) + if not valok then + table.insert(errors, string.format('value: %s', valexpr)) + end + + local addidx + if previdx and type(idx) == 'number' and idx - 1 == previdx then + -- monotonic sequence, do not print key + previdx = idx + addidx = false + else + -- end of monotonic sequence + -- from now on print keys as well + previdx = nil + addidx = true + end + + if addidx then + idxok, idxexpr, idxnote = pcall(self.val2expr, self, idx) + if not idxok or idxexpr == 'nil' then + table.insert(errors, string.format('key: not serializable', idxexpr)) + end + end + + local item = '' + if #errors == 0 then + -- finally serialize one [key=]?value expression + local indent = self:indent_head() + local note + if addidx then + note = self:format_note(idxnote, nil, self.key_val_sep) + item = string.format('%s%s[%s]%s=%s', + indent, note, + idxexpr, self.key_val_sep, self.key_val_sep) + indent = '' + end + note = self:format_note(valnote, nil, self.item_sep) + item = item .. string.format('%s%s%s,', indent, note, valexpr) + else + local errmsg = string.format('cannot print %s = %s (%s)', + self:string(tostring(idx)), + self:string(tostring(val)), + table.concat(errors, ', ')) + if self.on_unrepresentable == 'error' then + error(errmsg, 0) + else + errmsg = string.format('--[[ missing %s ]]', errmsg) + item = errmsg + end + end + table.insert(items, item) + table.remove(self.tab_key_path) -- pop current index from key path stack + end -- one key+value + self:indent_dec() + table.insert(items, self:indent_head() .. '}') + return table.concat(items, self.item_sep), string.format('%s follows', tab) +end + +-- machine readable variant, cannot represent all types and repeated references to a table +local serializer_class = { + indent_step = 0, + item_sep = ' ', + key_val_sep = ' ', + __inst_mt = {} +} +-- inhertance form base class (for :new()) +setmetatable(serializer_class, { __index = base_class }) +-- class instances with following metatable inherit all class members +serializer_class.__inst_mt.__index = serializer_class + +local function static_serializer(val, on_unrepresentable) + local inst = serializer_class:new(on_unrepresentable) + local expr, note = inst:val2expr(val) + return string.format('%s%s', inst:format_note(note, nil, inst.item_sep), expr) + end + +-- human friendly variant, not stable and not intended for machine consumption +local pprinter_class = { + indent_step = 4, + item_sep = '\n', + key_val_sep = ' ', + __inst_mt = {}, +} + +-- should be always empty because pretty-printer has fallback for all types +function pprinter_class.format_note() + return '' +end + +function pprinter_class._fallback(self, val) + if self.on_unrepresentable == 'error' then + base_class._fallback(self, val) + end + return tostring(val) +end + +function pprinter_class.char_is_printable(_, c) + -- ASCII (from space to ~) + tab or newline + -- and not ' or \ + return ((c >= 0x20 and c < 0x7f) + or c == 0x09 or c == 0x0A) + and c ~= 0x27 and c ~= 0x5C +end + +-- "function" is a Lua keyword so assignment below is workaround to create +-- function pprinter_class.function(self, f) +pprinter_class['function'] = function(self, f) +-- thanks to AnandA777 from StackOverflow! Function funcsign is adapted version of +-- https://stackoverflow.com/questions/51095022/inspect-function-signature-in-lua-5-1 + assert(type(f) == 'function', "bad argument #1 to 'funcsign' (function expected)") + local debuginfo = debug.getinfo(f) + local func_args = {} + local args_str + if debuginfo.what == 'C' then -- names N/A + args_str = '(?)' + goto add_name + end + + pcall(function() + local oldhook + local delay = 2 + local function hook() + delay = delay - 1 + if delay == 0 then -- call this only for the introspected function + -- stack depth 2 is the introspected function + for i = 1, debuginfo.nparams do + local k = debug.getlocal(2, i) + table.insert(func_args, k) + end + if debuginfo.isvararg then + table.insert(func_args, "...") + end + debug.sethook(oldhook) + error('aborting the call to introspected function') + end + end + oldhook = debug.sethook(hook, "c") -- invoke hook() on function call + f(unpack({})) -- huh? + end) + args_str = "(" .. table.concat(func_args, ", ") .. ")" + ::add_name:: + local name + if #self.tab_key_path > 0 then + name = string.format('function %s', self.tab_key_path[#self.tab_key_path]) + else + name = 'function ' + end + return string.format('%s%s: %s', name, args_str, string.sub(tostring(f), 11)) +end + +-- default tostring method is better suited for human-intended output +function pprinter_class.number(_, number) + return tostring(number) +end + +local function deserialize_lua(serial) + assert(type(serial) == 'string') + local deserial_func = loadstring('return ' .. serial) + if type(deserial_func) ~= 'function' then + panic('input is not a valid Lua expression') + end + return deserial_func() +end + +setmetatable(pprinter_class, { __index = base_class }) +pprinter_class.__inst_mt.__index = pprinter_class + +local function static_pprint(val, on_unrepresentable) + local inst = pprinter_class:new(on_unrepresentable) + local expr, note = inst:val2expr(val) + return string.format('%s%s', inst:format_note(note, nil, inst.item_sep), expr) +end + +local M = { + serialize_lua = static_serializer, + deserialize_lua = deserialize_lua, + pprint = static_pprint +} + +return M diff -Nru knot-resolver-5.1.1/daemon/lua/krprint.test.lua knot-resolver-5.2.1/daemon/lua/krprint.test.lua --- knot-resolver-5.1.1/daemon/lua/krprint.test.lua 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/lua/krprint.test.lua 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,292 @@ +local serialize_lua = require('krprint').serialize_lua +local deserialize_lua = require('krprint').deserialize_lua + +local function gen_string(maxlen) + maxlen = maxlen or 100 + local len = math.random(0, maxlen) + local buf = {} + for _=1,len do + table.insert(buf, string.char(math.random(0, 255))) + end + return table.concat(buf) +end + +local function test_de_serialization(orig_val, desc) + local serial = serialize_lua(orig_val) + ok(type(serial) == 'string' and #serial > 0, + 'serialization returns non-empty string: ' .. desc) + local deserial_val = deserialize_lua(serial) + same(type(orig_val), type(deserial_val), + 'deserialized value has the same type: ' .. desc) + if type(orig_val) == 'number' then + -- nan cannot be compared using == operator + if tostring(orig_val) == 'nan' and tostring(deserial_val) == 'nan' then + pass('nan value serialized and deserialized') + elseif orig_val ~= math.huge and orig_val ~= -math.huge then + -- tolerance measured experimentally on x86_64 LuaJIT 2.1.0-beta3 + local tolerance = 1e-14 + ok(math.abs(orig_val - deserial_val) <= tolerance, + 'deserialized number is within tolerance ' .. tolerance) + else + same(orig_val, deserial_val, 'deserialization returns the same infinity:' .. desc) + end + else + same(orig_val, deserial_val, + 'deserialization returns the same value: ' .. desc) + end +end + +local function test_de_serialization_autodesc(orig_val) + test_de_serialization(orig_val, tostring(orig_val)) +end + +local function test_bool() + test_de_serialization_autodesc(true) + same('true', table_print(true), 'table_print handles true') + test_de_serialization_autodesc(false) + same('false', table_print(false), 'table_print handles false') +end + +local function test_nil() + test_de_serialization_autodesc(nil) + same('nil', table_print(nil), 'table_print handles nil') +end + +local function gen_number_int() + local number + -- make "small" numbers more likely so they actually happen + if math.random() < 0.5 then + number = math.random(-2^32, 2^32) + else + number = math.random(-2^48, 2^48) + end + return number +end + +local function gen_number_float() + return math.random() +end + +local function test_number() + test_de_serialization_autodesc(0) + same('0', table_print(0), 'table_print handles 0') + test_de_serialization_autodesc(-math.huge) + same('-inf', table_print(-math.huge), 'table_print handles -infinity') + test_de_serialization_autodesc(math.huge) + same('inf', table_print(math.huge), 'table_print handles +infinity') + test_de_serialization_autodesc(tonumber('nan')) + same('nan', table_print(tonumber('nan')), 'table_print handles nan') + for _=1,20 do -- integers + test_de_serialization_autodesc(gen_number_int()) + -- bigger numbers might end up with non-exact representation + local smallnumber = math.random(-2^32, 2^32) + same(tostring(smallnumber), table_print(smallnumber), + 'table_print handles small numbers') + end + for _=1,20 do -- floats + local float = math.random() + same(tostring(float), table_print(float), + 'table_print handles floats') + test_de_serialization_autodesc(gen_number_float()) + end +end + +local function test_string() + test_de_serialization('', 'empty string') + for _=1,20 do + local str = gen_string(1024*10) + test_de_serialization(str, 'random string length ' .. #str) + end +end + +local function gen_number() + -- pure random would not produce special cases often enough + local generators = { + function() return 0 end, + function() return -math.huge end, + function() return math.huge end, + gen_number_int, + gen_number_float, + } + return generators[math.random(1, #generators)]() +end + +local function gen_boolean() + local options = {true, false} + return options[math.random(1, #options)] +end + +local function gen_table_atomic() + -- nil keys or values are not allowed + -- nested tables are handled elsewhere + local supported_types = { + gen_number, + gen_string, + gen_boolean, + } + val = supported_types[math.random(1, #supported_types)]() + return val +end + +local function gen_test_tables_supported(level) + level = level or 1 + local max_level = 5 + local max_items_per_table = 20 + local t = {} + for _=1, math.random(0, max_items_per_table) do + local val_as_table = (level <= max_level) and math.random() < 0.1 + local key, val + -- tapered.same method cannot compare keys with type table + key = gen_table_atomic() + if val_as_table then + val = gen_test_tables_supported(level + 1) + else + val = gen_table_atomic() + end + t[key] = val + end + return t +end + +local marker = 'this string must be present somewhere in output' +local function gen_marker() + return marker +end + +local kluautil = require('kluautil') +local function random_modify_table(t, always, generator) + assert(generator) + local tab_len = kluautil.kr_table_len(t) + local modified = false + -- modify some values + for key, val in pairs(t) do + if math.random(1, tab_len) == 1 then + if type(val) == 'table' then + modified = modified or random_modify_table(val, false, generator) + else + t[key] = generator() + modified = true + end + end + end + if always and not modified then + -- fallback, add an unsupported key + t[generator()] = true + modified = true + end + return modified +end + +local function test_table_supported() + for i=1,10 do + local t = gen_test_tables_supported() + test_de_serialization(t, 'random table no. ' .. i) + assert(random_modify_table(t, true, gen_marker)) + local str = table_print(t) + ok(string.find(str, marker, 1, true), + 'table_print works on complex serializable tables') + end +end + +local ffi = require('ffi') +local const_func = tostring +local const_thread = coroutine.create(tostring) +local const_userdata = ffi.C +local const_cdata = ffi.new('int') + +local function gen_unsupported_atomic() + -- nested tables are handled elsewhere + local unsupported_types = { + const_func, + const_thread, + const_userdata, + const_cdata + } + val = unsupported_types[math.random(1, #unsupported_types)] + return val +end + +local function test_unsupported(val, desc) + desc = desc or string.format('unsupported %s', type(val)) + return function() + boom(serialize_lua, { val, 'error' }, string.format( + 'attempt to serialize %s in error mode ' + .. 'causes error', desc)) + local output = serialize_lua(val, 'comment') + same('string', type(output), + string.format('attempt to serialize %s in ' + .. 'comment mode returned a string', + desc)) + ok(string.find(output, '--', 1, true), + 'returned string contains a comment') + output = table_print(val) + same('string', type(output), + string.format('table_print can stringify %s', desc)) + if type(val) ~= 'table' then + ok(string.find(output, type(val), 1, true), + 'exotic type is mentioned in table_print output') + end + end +end + +local function gen_test_tables_unsupported() + local t = gen_test_tables_supported() + random_modify_table(t, true, gen_unsupported_atomic) + return t +end + +local function test_unsupported_table() + for i=1,10 do + local t = gen_test_tables_unsupported() + test_unsupported(t, 'random unsupported table no. ' .. i)() + assert(random_modify_table(t, true, gen_marker)) + local str = table_print(t) + ok(string.find(str, marker, 1, true), + 'table_print works on complex unserializable tables') + end +end + +local function func_2vararg_5ret(arg1, arg2, ...) + return select('#', ...), nil, arg1 + arg2, false, nil +end +local function func_ret_nil() return nil end +local function func_ret_nothing() return end + +local function test_pprint_func() + local t = { [false] = func_2vararg_5ret } + local output = table_print(t) + ok(string.find(output, 'function false(arg1, arg2, ...)', 1, true), + 'function parameters are pretty printed') +end + +local function test_pprint_func_ret() + local output = table_print(func_2vararg_5ret(1, 2, 'bla')) + local exp = [[ +1 -- result # 1 +nil -- result # 2 +3 -- result # 3 +false -- result # 4 +nil -- result # 5]] + same(output, exp, 'multiple return values are pretty printed') + + output = table_print(func_ret_nil()) + same(output, 'nil', 'single return value does not have extra comments') + + output = table_print(func_ret_nothing()) + same(output, nil, 'no return values to be printed cause nil output') +end + +return { + test_bool, + test_nil, + test_number, + test_string, + test_table_supported, + test_unsupported(const_func), + test_unsupported(const_thread), + test_unsupported(const_userdata), + test_unsupported(const_cdata), + test_unsupported_table, + test_pprint_func, + test_pprint_func_ret, +} diff -Nru knot-resolver-5.1.1/daemon/lua/map.test.integr/deckard.yaml knot-resolver-5.2.1/daemon/lua/map.test.integr/deckard.yaml --- knot-resolver-5.1.1/daemon/lua/map.test.integr/deckard.yaml 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/lua/map.test.integr/deckard.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +programs: +- name: kresd3 + binary: kresd + additional: + - --noninteractive + templates: + - daemon/lua/map.test.integr/kresd_config.j2 + - tests/integration/hints_zone.j2 + - tests/config/tapered/src/tapered.lua + configs: + - config + - hints + - tapered.lua +- name: kresd2 + binary: kresd + additional: + - --noninteractive + templates: + - daemon/lua/map.test.integr/kresd_config.j2 + - tests/integration/hints_zone.j2 + - tests/config/tapered/src/tapered.lua + configs: + - config + - hints + - tapered.lua +- name: kresd1 + binary: kresd + additional: + - --noninteractive + templates: + - daemon/lua/map.test.integr/kresd_config.j2 + - tests/integration/hints_zone.j2 + - tests/config/tapered/src/tapered.lua + configs: + - config + - hints + - tapered.lua diff -Nru knot-resolver-5.1.1/daemon/lua/map.test.integr/kresd_config.j2 knot-resolver-5.2.1/daemon/lua/map.test.integr/kresd_config.j2 --- knot-resolver-5.1.1/daemon/lua/map.test.integr/kresd_config.j2 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/lua/map.test.integr/kresd_config.j2 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,192 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +log('my PID = %d', worker.pid) + +trust_anchors.remove('.') + +cache.size = 2*MB + +net = { '{{SELF_ADDR}}' } + +{% if QMIN == "false" %} +option('NO_MINIMIZE', true) +{% else %} +option('NO_MINIMIZE', false) +{% endif %} + +-- Self-checks on globals +assert(help() ~= nil) +assert(worker.id ~= nil) +-- Self-checks on facilities +assert(cache.count() == 0) +assert(cache.stats() ~= nil) +assert(cache.backends() ~= nil) +assert(worker.stats() ~= nil) +assert(net.interfaces() ~= nil) +-- Self-checks on loaded stuff +assert(#modules.list() > 0) +-- Self-check timers +ev = event.recurrent(1 * sec, function (ev) return 1 end) +event.cancel(ev) + +local kluautil = require('kluautil') +local tap = require('tapered') +local checks_total = 16 +local n_instances = 3 -- must match deckard.yaml + +worker.control_path = worker.cwd .. '/../kresd3/control/' +net.listen(worker.control_path .. worker.pid, nil, {kind = 'control'}) +assert(#net.list() >= 3) -- UDP, TCP, control + +-- debug, kept for future use +--log('%s', worker.control_path) +--log('%s', table_print(net.list())) + +function wait_for_sockets() + log('waiting for control sockets') + local ffi = require('ffi') + local timeout = 5000 -- ms + local start_time = tonumber(ffi.C.kr_now()) + local now + while true do + now = tonumber(ffi.C.kr_now()) + if now > start_time + timeout then + log('timeout while waiting for control sockets to appear') + os.exit(3) + end + local pids = kluautil.list_dir(worker.control_path) + if #pids == n_instances then + -- debug, kept for future use + --log('got control sockets:') + --log(table_print(pids)) + break + else + worker.sleep(0.1) + end + end + log('PIDs are visible now (waiting took %d ms)', now - start_time) +end + +-- expression should throw Lua error: +-- wrap it in a function which runs the expression on leader and follower +-- separately so we can guarantee both cases are covered +function boom_follower_and_leader(boom_expr, desc) + local variants = {leader = '~=', follower = '=='} + for name, operator in pairs(variants) do + -- beware, newline is not allowed in expr + local full_expr = string.format( + 'if (worker.pid %s %s) then return true ' + .. 'else return %s end', + operator, worker.pid, boom_expr) + local full_desc = name .. ': ' + if desc then + full_desc = full_desc .. desc .. ' (' .. boom_expr .. ')' + else + full_desc = full_desc .. boom_expr + end + tap.boom(map, {full_expr}, full_desc) + end +end + +function tests() + -- add delay to each test to force scheduler to interleave tests and DNS queries + local test_delay = 20 / 1000 -- seconds + log('starting map() tests now') + + tap.boom(map, {'1 ++ 1'}, 'syntax error in command is detected') + worker.sleep(test_delay) + + -- array of integers + local pids = map('worker.pid') + tap.same(pids.n, n_instances, 'all pids were obtained') + table.sort(pids) + worker.sleep(test_delay) + + -- expression produces array of integers + local pids_plus_one = map('worker.pid + 1') + tap.same(pids_plus_one.n, n_instances, 'all pids were obtained') + table.sort(pids_plus_one) + for idx=1,n_instances do + tap.same(pids[idx] + 1, pids_plus_one[idx], + 'increment expression worked') + end + worker.sleep(test_delay) + + -- error detection + boom_follower_and_leader('error("explosion")') + worker.sleep(test_delay) + + -- unsupported number of return values + boom_follower_and_leader('1, 2') + worker.sleep(test_delay) + boom_follower_and_leader('unpack({})') + worker.sleep(test_delay) + + -- unsupported return type + boom_follower_and_leader( + 'function() print("this cannot be serialized") end') + worker.sleep(test_delay) + + tap.same({n = n_instances}, map('nil'), + 'nil values are counted as returned') + worker.sleep(test_delay) + + local exp = {n = n_instances} + for i=1,n_instances do + table.insert(exp, {nil, 2, nil, n=3}) + end + local got = map('require("kluautil").kr_table_pack(nil, 2, nil)') + tap.same(got, exp, 'kr_table_pack handles nil values') + worker.sleep(test_delay) +end + +local started = false +function tests_start() + -- just in case, duplicates should not happen + if started then + log('huh? duplicate test invocation ignored, a retrasmit?') + return + end + started = true + log('start query triggered, scheduling tests') + + -- DNS queries and map() commands must be serviced while sleep is running + worker.coroutine(function() worker.sleep(3600) end) + + worker.coroutine(tests) +end +-- Deckard query will trigger tests +policy.add(policy.suffix(tests_start, {'\5start\0'})) + +function tests_done() + print('final query triggered') + event.after(0, function() + tap.done(checks_total) + end) +end +-- Deckard query will execute tap.done() which will call os.exit() +-- i.e. this callback has to be called only after answer to Deckard was sent +policy.add(policy.suffix(tests_done, {'\4done\0'}), true) + +-- add delay to each query to force scheduler to interleave tests and DNS queries +policy.add(policy.all( + function() + local delay = 10 -- ms + log('packet delayed by %d ms', delay) + worker.sleep(delay / 1000) + end)) + +wait_for_sockets() + +{% if DAEMON_NAME == "kresd1" %} + +-- forward to Deckard test server +policy.add(policy.all(policy.FORWARD('192.0.2.1'))) + +{% else %} + +-- forward to next kresd instance in chain +{# find out IP address of kresd instance with lower number, + i.e. kresd2 forwards to kresd1 #} +policy.add(policy.all(policy.FORWARD('{{ PROGRAMS[ "kresd" ~ (DAEMON_NAME[-1]|int() - 1)]["address"] }}'))) + +{% endif %} diff -Nru knot-resolver-5.1.1/daemon/lua/map.test.integr/query-while-map-is-running.rpl knot-resolver-5.2.1/daemon/lua/map.test.integr/query-while-map-is-running.rpl --- knot-resolver-5.1.1/daemon/lua/map.test.integr/query-while-map-is-running.rpl 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/lua/map.test.integr/query-while-map-is-running.rpl 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,312 @@ +; does not make any practical difference so we limit ourselves to single test run +query-minimization: off +CONFIG_END + +SCENARIO_BEGIN Empty answers to any query - forwarding without validation + +; forwarding target +RANGE_BEGIN 1 1000000 + ADDRESS 192.0.2.1 + +; NODATA to everything +ENTRY_BEGIN +MATCH opcode +ADJUST copy_id copy_query +REPLY NOERROR QR +SECTION QUESTION +. IN SOA +SECTION ANSWER +. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400 +ENTRY_END +RANGE_END + +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +start. IN TXT +ENTRY_END + +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +start. IN TXT +SECTION ANSWER +ENTRY_END + + +STEP 1001 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1001. IN TXT +ENTRY_END + +STEP 1002 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1001. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1003 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1003. IN TXT +ENTRY_END + +STEP 1004 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1003. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1005 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1005. IN TXT +ENTRY_END + +STEP 1006 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1005. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1007 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1007. IN TXT +ENTRY_END + +STEP 1008 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1007. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1009 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1009. IN TXT +ENTRY_END + +STEP 1010 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1009. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1011 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1011. IN TXT +ENTRY_END + +STEP 1012 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1011. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1013 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1013. IN TXT +ENTRY_END + +STEP 1014 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1013. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1015 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1015. IN TXT +ENTRY_END + +STEP 1016 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1015. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1017 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1017. IN TXT +ENTRY_END + +STEP 1018 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1017. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1019 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1019. IN TXT +ENTRY_END + +STEP 1020 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1019. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1021 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1021. IN TXT +ENTRY_END + +STEP 1022 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1021. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1023 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1023. IN TXT +ENTRY_END + +STEP 1024 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1023. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1025 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1025. IN TXT +ENTRY_END + +STEP 1026 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1025. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1027 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1027. IN TXT +ENTRY_END + +STEP 1028 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1027. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1029 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1029. IN TXT +ENTRY_END + +STEP 1030 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1029. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1031 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1031. IN TXT +ENTRY_END + +STEP 1032 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1031. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1033 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +done. IN TXT +ENTRY_END + +STEP 1034 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +done. IN TXT +SECTION ANSWER +ENTRY_END + +SCENARIO_END diff -Nru knot-resolver-5.1.1/daemon/lua/meson.build knot-resolver-5.2.1/daemon/lua/meson.build --- knot-resolver-5.1.1/daemon/lua/meson.build 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/lua/meson.build 2020-12-09 09:44:29.000000000 +0000 @@ -2,10 +2,16 @@ # SPDX-License-Identifier: GPL-3.0-or-later config_tests += [ + ['controlsock', files('controlsock.test.lua')], + ['krprint', files('krprint.test.lua')], ['ta', files('trust_anchors.test/ta.test.lua')], ['ta_bootstrap', files('trust_anchors.test/bootstrap.test.lua')], ] +integr_tests += [ + ['map', meson.current_source_dir() / 'map.test.integr'], +] + lua_config = configuration_data() lua_config.set('keyfile_default', keyfile_default) lua_config.set('etc_dir', etc_dir) @@ -44,6 +50,7 @@ trust_anchors, files('zonefile.lua'), files('kluautil.lua'), + files('krprint.lua'), distro_preconfig, ] diff -Nru knot-resolver-5.1.1/daemon/lua/postconfig.lua knot-resolver-5.2.1/daemon/lua/postconfig.lua --- knot-resolver-5.1.1/daemon/lua/postconfig.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/lua/postconfig.lua 2020-12-09 09:44:29.000000000 +0000 @@ -9,7 +9,11 @@ for _, socket in ipairs(net.list()) do if socket.kind == 'control' then control_socks = control_socks + 1 - elseif socket.kind == 'dns' or socket.kind == 'tls' or socket.kind == 'doh' then + elseif (socket.kind == 'dns' or + socket.kind == 'xdp' or + socket.kind == 'tls' or + socket.kind == 'doh' or + socket.kind == 'doh2') then dns_socks = dns_socks + 1 end end @@ -18,9 +22,12 @@ local n_dns_socks, n_control_socks = count_sockets() +-- Check and set control sockets path +worker.control_path = worker.control_path or (worker.cwd .. '/control/') + -- Bind to control socket by default -if not C.the_args.interactive and n_control_socks == 0 and not env.KRESD_NO_LISTEN then - local path = worker.cwd..'/control/'..worker.pid +if n_control_socks == 0 and not env.KRESD_NO_LISTEN then + local path = worker.control_path..worker.pid local ok, err = pcall(net.listen, path, nil, { kind = 'control' }) if not ok then warn('bind to '..path..' failed '..err) diff -Nru knot-resolver-5.1.1/daemon/lua/sandbox.lua.in knot-resolver-5.2.1/daemon/lua/sandbox.lua.in --- knot-resolver-5.1.1/daemon/lua/sandbox.lua.in 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/lua/sandbox.lua.in 2020-12-09 09:44:29.000000000 +0000 @@ -2,6 +2,8 @@ local debug = require('debug') local ffi = require('ffi') +local kluautil = require('kluautil') +local krprint = require("krprint") -- Units kB = 1024 @@ -17,7 +19,7 @@ -- Logging function panic(fmt, ...) - print(debug.traceback('error occured here (config filename:lineno is ' + print(debug.traceback('error occurred here (config filename:lineno is ' .. 'at the bottom, if config is involved):', 2)) error(string.format('ERROR: '.. fmt, ...), 0) end @@ -199,7 +201,7 @@ elseif arg ~= nil then arg_conv = tostring(arg) end - local ret_cstr = cb(__engine, kr_module, arg_conv) + local ret_cstr = cb(ffi.C.the_worker.engine, kr_module, arg_conv) if ret_cstr == nil then return nil end @@ -446,8 +448,7 @@ -- Load keyfile_default trust_anchors.add_file('@keyfile_default@', @unmanaged@) --- Interactive command evaluation -function eval_cmd(line, raw) +local function eval_cmd_compile(line, raw) -- Compatibility sandbox code loading local function load_code(code) if getfenv then -- Lua 5.1 @@ -461,6 +462,12 @@ if err then chunk, err = load_code(line) end + return chunk, err +end + +-- Interactive command evaluation +function eval_cmd(line, raw) + local chunk, err = eval_cmd_compile(line, raw) if not err then return chunk() else @@ -469,102 +476,23 @@ end -- Pretty printing - -local function funcsign(f) --- thanks to AnandA777 from StackOverflow! Function funcsign is adapted version of --- https://stackoverflow.com/questions/51095022/inspect-function-signature-in-lua-5-1 - assert(type(f) == 'function', "bad argument #1 to 'funcsign' (function expected)") - local debuginfo = debug.getinfo(f) - if debuginfo.what == 'C' then -- names N/A - return '(?)' - end - - local func_args = {} - pcall(function() - local oldhook - local delay = 2 - local function hook() - delay = delay - 1 - if delay == 0 then -- call this only for the introspected function - -- stack depth 2 is the introspected function - for i = 1, debuginfo.nparams do - local k = debug.getlocal(2, i) - table.insert(func_args, k) - end - if debuginfo.isvararg then - table.insert(func_args, "...") - end - debug.sethook(oldhook) - error('aborting the call to introspected function') - end - end - oldhook = debug.sethook(hook, "c") -- invoke hook() on function call - f(unpack({})) -- huh? - end) - return "(" .. table.concat(func_args, ", ") .. ")" -end - -function table_print (tt, indent, done) - done = done or {} - indent = indent or 0 - local result = "" - -- Ordered for-iterator for tables with tostring-able keys. - local function ordered_iter(unordered_tt) - local keys = {} - for k in pairs(unordered_tt) do - table.insert(keys, k) - end - table.sort(keys, function (a, b) return tostring(a) < tostring(b) end) - local i = 0 - return function() - i = i + 1 - if keys[i] then - return keys[i], unordered_tt[keys[i]] - end - end - end - -- Convert to printable string (escape unprintable) - local function printable(value) - value = tostring(value) - local bytes = {} - for i = 1, #value do - local c = string.byte(value, i) - if c >= 0x20 and c < 0x7f then table.insert(bytes, string.char(c)) - else table.insert(bytes, '\\'..tostring(c)) - end - if i > 80 then table.insert(bytes, '...') break end - end - return table.concat(bytes) - end - if type(tt) == "table" then - for key, value in ordered_iter(tt) do - result = result .. string.rep (" ", indent) - if type (value) == "table" and not done [value] then - done [value] = true - result = result .. string.format("[%s] => {\n", printable (key)) - result = result .. table_print (value, indent + 4, done) - result = result .. string.rep (" ", indent) - result = result .. "}\n" - elseif type (value) == "function" then - result = result .. string.format("[%s] => function %s%s: %s\n", - tostring(key), tostring(key), funcsign(value), - string.sub(tostring(value), 11)) - else - result = result .. string.format("[%s] => %s\n", - tostring (key), printable(value)) - end - end - else -- not a table - local tt_str - if type(tt) == "function" then - tt_str = string.format("function%s: %s\n", funcsign(tt), - string.sub(tostring(tt), 11)) +local pprint = require('krprint').pprint +function table_print(...) + local strs = {} + local nargs = select('#', ...) + if nargs == 0 then + return nil + end + for n=1,nargs do + local arg = select(n, ...) + local arg_str = pprint(arg) + if nargs > 1 then + table.insert(strs, string.format("%s\t-- result # %d", arg_str, n)) else - tt_str = tostring(tt) + table.insert(strs, arg_str) end - result = result .. tt_str .. "\n" end - return result + return table.concat(strs, '\n') end -- This extends the worker module to allow asynchronous execution of functions and nonblocking I/O. @@ -642,3 +570,204 @@ worker.coroutine = disabled worker.bg_worker = setmetatable({}, { __index = disabled }) end + +-- Global commands for map() + +-- must be public because it is called from eval_cmd() +-- when map() commands are read from control socket +function _map_luaobj_call_wrapper(cmd) + local func = eval_cmd_compile(cmd, true) + local ret = kluautil.kr_table_pack(xpcall(func, debug.traceback)) + local ok, serial = pcall(krprint.serialize_lua, ret, 'error') + if not ok then + if verbose() then + log('failed to serialize map() response %s (%s)', + table_print(ret), serial) + end + return krprint.serialize_lua( + kluautil.kr_table_pack(false, "returned values cannot be serialized: " + .. serial)) + else + return serial + end +end + +local function _sock_errmsg(path, desc) + return string.format( + 'map() error while communicating with %s: %s', + path, desc) +end + +local function _sock_check(sock, call, params, path, desc) + local errprefix = _sock_errmsg(path, desc) .. ': ' + local retvals = kluautil.kr_table_pack(pcall(call, unpack(params))) + local ok = retvals[1] + if not ok then + error(errprefix .. tostring(retvals[2])) + end + local rerr, werr = sock:error() + if rerr or werr then + error(string.format('%sread error %s; write error %s', errprefix, rerr, werr)) + end + if retvals[2] == nil then + error(errprefix .. 'unexpected nil result') + end + return unpack(retvals, 2, retvals.n) +end + +local function _sock_assert(condition, path, desc) + if not condition then + error(_sock_errmsg(path, desc)) + end +end + +local function map_send_recv(cmd, path) + local bit = require('bit') + local socket = require('cqueues.socket') + local s = socket.connect({ path = path }) + s:setmaxerrs(0) + s:setmode('bn', 'bn') + local status, err = pcall(s.connect, s) + if not status then + log('map() error while connecting to control socket %s: ' + .. '%s (ignoring this socket)', path, err) + return nil + end + local ret = _sock_check(s, s.write, {s, '__binary\n'}, path, + 'write __binary') + _sock_assert(ret, path, + 'write __binary result') + local recv = _sock_check(s, s.read, {s, 2}, path, + 'read reply to __binary') + _sock_assert(recv and recv == '> ', path, + 'unexpected reply to __binary') + _sock_check(s, s.write, {s, cmd..'\n'}, path, + 'command write') + recv = _sock_check(s, s.read, {s, 4}, path, + 'response length read') + _sock_assert(recv and #recv == 4, path, + 'length of response length preambule does not match') + local len = tonumber(recv:byte(1)) + for i=2,4 do + len = bit.bor(bit.lshift(len, 8), tonumber(recv:byte(i))) + end + ret = _sock_check(s, s.read, {s, len}, path, + 'read response') + _sock_assert(ret and #ret == len, path, + 'actual response length does not match length in preambule') + s:close() + return ret +end + +-- internal use only +-- Call cmd on each instance via control sockets. +-- @param format - "luaobj" if individual results should be Lua objects +-- - "strings" for eval_cmd output for each instance +-- @returns table with results, one item per instance + key n=number of instances +-- (order of return values is undefined) +-- @throws Lua error if: +-- - communication failed in the middle of trasaction +-- - a result is not serializable +-- - individual call throws an error +-- - number of return values != 1 per instance per call +-- - cmd execution state is undefined after an error +-- Connection errors at the beginning are ignored to paper over leftover dead sockets. +function map(cmd, format) + local local_sockets = {} + local results = {} + + if (type(cmd) ~= 'string') then + panic('map() command must be a string') end + if string.find(cmd, '\n', 1, true) then + panic('map() command cannot contain literal \\n, escape it with \\010') end + if (#cmd <= 0) then + panic('map() command must be non-empty') end + -- syntax check on input command to detect typos early + local chunk, err = eval_cmd_compile(cmd, false) + if not chunk then + panic('failure when compiling map() command: %s', err) + end + + format = format or 'luaobj' + if (format ~= 'luaobj' and format ~= 'strings') then + panic('map() output format must be luaobj or strings') end + if format == 'luaobj' then + cmd = '_map_luaobj_call_wrapper([=====[' .. cmd .. ']=====])' + end + + -- find out control socket paths + for _,v in pairs(net.list()) do + if (v['kind'] == 'control') and (v['transport']['family'] == 'unix') then + table.insert(local_sockets, string.match(v['transport']['path'], '^.*/([^/]+)$')) + end + end + local filetab = kluautil.list_dir(worker.control_path) + if next(filetab) == nil then + panic('no control sockets found in directory %s', + worker.control_path) + end + + local result_count = 0 + -- finally execute it on all instances + for _, file in ipairs(filetab) do + local local_exec = false + for _, lsoc in ipairs(local_sockets) do + if file == lsoc then + local_exec = true + end + end + local path = worker.control_path..file + local path_name = (local_exec and 'this instance') or path + if verbose() then + log('executing map() on %s: command %s', path_name, cmd) + end + local ret + if local_exec then + ret = eval_cmd(cmd) + else + ret = map_send_recv(cmd, path) + -- skip dead sockets (leftovers from dead instances) + if ret == nil then + goto continue + end + end + result_count = result_count + 1 + -- return value is output from eval_cmd + -- i.e. string including "quotes" and Lua escaping in between + assert(type(ret) == 'string', 'map() protocol error, ' + .. 'string not retured by follower') + assert(#ret >= 2 and + string.sub(ret, 1, 1) == "'" + and string.sub(ret, -1, -1) == "'", + 'map() protocol error, value returned by follower does ' + .. 'not look like a string') + -- deserialize string: remove "quotes" and de-escape bytes + ret = krprint.deserialize_lua(ret) + if format == 'luaobj' then + -- ret should be table with xpcall results serialized into string + ret = krprint.deserialize_lua(ret) + assert(type(ret) == 'table', 'map() protocol error, ' + .. 'table with results not retured by follower') + if (ret.n ~= 2) then + if verbose() then + log('got unsupported map() response: %s', table_print(ret)) + end + panic('unexpected number of return values in map() response: ' + .. 'only single return value is allowed, ' + .. 'use kluautil.kr_table_pack() helper') + end + local ok, retval = ret[1], ret[2] + if ok == false then + panic('error when executing map() command on control socket %s: ' + .. '%s. command execution state is now undefined!', + path, retval) + end + -- drop wrapper table and return only the actual return value + ret = retval + end + results[result_count] = ret + ::continue:: + end + results.n = result_count + return results +end diff -Nru knot-resolver-5.1.1/daemon/lua/trust_anchors.lua.in knot-resolver-5.2.1/daemon/lua/trust_anchors.lua.in --- knot-resolver-5.1.1/daemon/lua/trust_anchors.lua.in 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/lua/trust_anchors.lua.in 2020-12-09 09:44:29.000000000 +0000 @@ -311,11 +311,10 @@ local function add_file(path, unmanaged) local managed = not unmanaged + if not ta_update then + modules.load('ta_update') + end if managed then - if not ta_update then - panic('[ ta ] automatic update for ' .. path .. ' requested, ' - .. 'but required module ta_update is not loaded') - end if not io.open(path .. '.lock', 'w') then error("[ ta ] ERROR: write access needed to keyfile dir '"..path.."'") end @@ -367,9 +366,7 @@ end -- TODO: if failed and for root, try to rebootstrap? - if managed then - ta_update.start(owner) - end + ta_update.start(owner, managed) end local function remove(zname) diff -Nru knot-resolver-5.1.1/daemon/lua/trust_anchors.test/bootstrap.test.lua knot-resolver-5.2.1/daemon/lua/trust_anchors.test/bootstrap.test.lua --- knot-resolver-5.1.1/daemon/lua/trust_anchors.test/bootstrap.test.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/lua/trust_anchors.test/bootstrap.test.lua 2020-12-09 09:44:29.000000000 +0000 @@ -1,10 +1,11 @@ -- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('ta_update') -- check prerequisites local has_http = pcall(require, 'kres_modules.http') and pcall(require, 'http.request') if not has_http then - pass('skipping bootstrap tests because http module is not not installed') - done() + -- skipping bootstrap tests because http module is not not installed + os.exit(77) end local cqueues = require("cqueues") diff -Nru knot-resolver-5.1.1/daemon/main.c knot-resolver-5.2.1/daemon/main.c --- knot-resolver-5.1.1/daemon/main.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/main.c 2020-12-09 09:44:29.000000000 +0000 @@ -28,13 +28,13 @@ #include #include -#ifdef ENABLE_CAP_NG +#if ENABLE_CAP_NG #include #endif #include #include -#if SYSTEMD_VERSION > 0 +#if ENABLE_LIBSYSTEMD #include #endif #include @@ -42,97 +42,6 @@ struct args the_args_value; /** Static allocation for the_args singleton. */ - -/* @internal AF_LOCAL reads may still be interrupted, loop it. */ -static bool ipc_readall(int fd, char *dst, size_t len) -{ - while (len > 0) { - int rb = read(fd, dst, len); - if (rb > 0) { - dst += rb; - len -= rb; - } else if (errno != EAGAIN && errno != EINTR) { - return false; - } - } - return true; -} - -static void ipc_activity(uv_poll_t *handle, int status, int events) -{ - struct engine *engine = handle->data; - if (status != 0) { - kr_log_error("[system] ipc: %s\n", uv_strerror(status)); - return; - } - /* Get file descriptor from handle */ - uv_os_fd_t fd = 0; - (void) uv_fileno((uv_handle_t *)(handle), &fd); - /* Read expression from IPC pipe */ - uint32_t len = 0; - auto_free char *rbuf = NULL; - if (!ipc_readall(fd, (char *)&len, sizeof(len))) { - goto failure; - } - if (len < UINT32_MAX) { - rbuf = malloc(len + 1); - } else { - errno = EINVAL; - } - if (!rbuf) { - goto failure; - } - if (!ipc_readall(fd, rbuf, len)) { - goto failure; - } - rbuf[len] = '\0'; - /* Run expression */ - const char *message = ""; - int ret = engine_ipc(engine, rbuf); - if (ret > 0) { - message = lua_tostring(engine->L, -1); - } - /* Clear the Lua stack */ - lua_settop(engine->L, 0); - /* Send response back */ - len = strlen(message); - if (write(fd, &len, sizeof(len)) != sizeof(len) || - write(fd, message, len) != len) { - goto failure; - } - return; /* success! */ -failure: - /* Note that if the piped command got read or written partially, - * we would get out of sync and only receive rubbish now. - * Therefore we prefer to stop IPC, but we try to continue with all else. - */ - kr_log_error("[system] stopping ipc because of: %s\n", strerror(errno)); - uv_poll_stop(handle); - uv_close((uv_handle_t *)handle, (uv_close_cb)free); -} - -static bool ipc_watch(uv_loop_t *loop, struct engine *engine, int fd) -{ - uv_poll_t *poller = malloc(sizeof(*poller)); - if (!poller) { - return false; - } - int ret = uv_poll_init(loop, poller, fd); - if (ret != 0) { - free(poller); - return false; - } - poller->data = engine; - ret = uv_poll_start(poller, UV_READABLE, ipc_activity); - if (ret != 0) { - free(poller); - return false; - } - /* libuv sets O_NONBLOCK whether we want it or not */ - (void) fcntl(fd, F_SETFD, fcntl(fd, F_GETFL) & ~O_NONBLOCK); - return true; -} - static void signal_handler(uv_signal_t *handle, int signum) { uv_stop(uv_default_loop()); @@ -180,15 +89,10 @@ * Server operation. */ -static int fork_workers(fd_array_t *ipc_set, int forks) +static int fork_workers(int forks) { /* Fork subprocesses if requested */ while (--forks > 0) { - int sv[2] = {-1, -1}; - if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sv) < 0) { - perror("[system] socketpair"); - return kr_error(errno); - } int pid = fork(); if (pid < 0) { perror("[system] fork"); @@ -197,16 +101,7 @@ /* Forked process */ if (pid == 0) { - array_clear(*ipc_set); - array_push(*ipc_set, sv[0]); - close(sv[1]); return forks; - /* Parent process */ - } else { - array_push(*ipc_set, sv[1]); - /* Do not share parent-end with other forks. */ - (void) fcntl(sv[1], F_SETFD, FD_CLOEXEC); - close(sv[0]); } } return 0; @@ -234,7 +129,7 @@ } /** \return exit code for main() */ -static int run_worker(uv_loop_t *loop, struct engine *engine, fd_array_t *ipc_set, bool leader, struct args *args) +static int run_worker(uv_loop_t *loop, struct engine *engine, bool leader, struct args *args) { /* Only some kinds of stdin work with uv_pipe_t. * Otherwise we would abort() from libuv e.g. with interactive) { if (!args->quiet) printf("[system] interactive mode\n> "); - uv_pipe_open(&pipe, 0); - uv_read_start((uv_stream_t*) &pipe, io_tty_alloc, io_tty_process_input); - } else if (args->control_fd != -1 && uv_pipe_open(&pipe, args->control_fd) == 0) { - uv_listen((uv_stream_t *) &pipe, 16, io_tty_accept); - } - /* Watch IPC pipes (or just assign them if leading the pgroup). */ - if (!leader) { - for (size_t i = 0; i < ipc_set->len; ++i) { - if (!ipc_watch(loop, engine, ipc_set->at[i])) { - kr_log_error("[system] failed to create poller: %s\n", strerror(errno)); - close(ipc_set->at[i]); - } - } + pipe->data = io_tty_alloc_data(); + uv_pipe_open(pipe, 0); + uv_read_start((uv_stream_t*)pipe, io_tty_alloc, io_tty_process_input); + } else if (args->control_fd != -1 && uv_pipe_open(pipe, args->control_fd) == 0) { + uv_listen((uv_stream_t *)pipe, 16, io_tty_accept); } - memcpy(&engine->ipc_set, ipc_set, sizeof(*ipc_set)); /* Notify supervisor. */ -#if SYSTEMD_VERSION > 0 +#if ENABLE_LIBSYSTEMD sd_notify(0, "READY=1"); #endif /* Run event loop */ uv_run(loop, UV_RUN_DEFAULT); - uv_close((uv_handle_t *)&pipe, NULL); /* Seems OK even on the stopped loop. */ + /* Free pipe's data. Seems OK even on the stopped loop. + * In interactive case it may have been done in callbacks already (single leak). */ + if (!args->interactive) { + uv_close((uv_handle_t *)pipe, NULL); + free(pipe); + } return EXIT_SUCCESS; } @@ -490,8 +380,12 @@ /* Drop POSIX 1003.1e capabilities. */ static void drop_capabilities(void) { -#ifdef ENABLE_CAP_NG - /* Drop all capabilities. */ +#if ENABLE_CAP_NG + /* Drop all capabilities when running under non-root user. */ + if (geteuid() == 0) { + kr_log_verbose("[system] running as root, no capabilities dropped\n"); + return; + } if (capng_have_capability(CAPNG_EFFECTIVE, CAP_SETPCAP)) { capng_clear(CAPNG_SELECT_BOTH); @@ -499,9 +393,12 @@ if (capng_apply(CAPNG_SELECT_BOTH) < 0) { kr_log_error("[system] failed to set process capabilities: %s\n", strerror(errno)); + } else { + kr_log_verbose("[system] all capabilities dropped\n"); } } else { - kr_log_info("[system] process not allowed to set capabilities, skipping\n"); + /* If user() was called, the capabilities were already dropped along with SETPCAP. */ + kr_log_verbose("[system] process not allowed to set capabilities, skipping\n"); } #endif /* ENABLE_CAP_NG */ } @@ -515,7 +412,7 @@ } if (strcmp("linux", OPERATING_SYSTEM) != 0) kr_log_info("[warn] Knot Resolver is tested on Linux, other platforms might exhibit bugs.\n" - "Please report issues to https://gitlab.labs.nic.cz/knot/knot-resolver/issues/\n" + "Please report issues to https://gitlab.nic.cz/knot/knot-resolver/issues/\n" "Thank you for your time and interest!\n"); the_args = &the_args_value; @@ -579,11 +476,8 @@ (long)rlim.rlim_cur); } - /* Connect forks with local socket */ - fd_array_t ipc_set; - array_init(ipc_set); /* Fork subprocesses if requested */ - int fork_id = fork_workers(&ipc_set, the_args->forks); + int fork_id = fork_workers(the_args->forks); if (fork_id < 0) { return EXIT_FAILURE; } @@ -595,8 +489,6 @@ .ctx = mp_new (4096), .alloc = (knot_mm_alloc_t) mp_alloc }; - /** Static to work around lua_pushlightuserdata() limitations. - * TODO: convert to a proper singleton like worker, most likely. */ static struct engine engine; ret = engine_init(&engine, &pool); if (ret != 0) { @@ -604,7 +496,7 @@ return EXIT_FAILURE; } /* Initialize the worker */ - ret = worker_init(&engine, fork_id, the_args->forks); + ret = worker_init(&engine, the_args->forks); if (ret != 0) { kr_log_error("[system] failed to initialize worker: %s\n", kr_strerror(ret)); return EXIT_FAILURE; @@ -686,7 +578,7 @@ } /* Run the event loop */ - ret = run_worker(loop, &engine, &ipc_set, fork_id == 0, the_args); + ret = run_worker(loop, &engine, fork_id == 0, the_args); cleanup:/* Cleanup. */ engine_deinit(&engine); diff -Nru knot-resolver-5.1.1/daemon/meson.build knot-resolver-5.2.1/daemon/meson.build --- knot-resolver-5.1.1/daemon/meson.build 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/meson.build 2020-12-09 09:44:29.000000000 +0000 @@ -21,6 +21,10 @@ 'worker.c', 'zimport.c', ]) +if nghttp2.found() + kresd_src += files(['http.c']) +endif + c_src_lint += kresd_src config_tests += [ @@ -28,7 +32,7 @@ ] integr_tests += [ - ['cache_insert_ns', join_paths(meson.current_source_dir(), 'cache.test', 'insert_ns.test.integr')] + ['cache_insert_ns', meson.current_source_dir() / 'cache.test' / 'insert_ns.test.integr'] ] kresd_deps = [ @@ -43,6 +47,7 @@ gnutls, libsystemd, capng, + nghttp2, ] diff -Nru knot-resolver-5.1.1/daemon/network.c knot-resolver-5.2.1/daemon/network.c --- knot-resolver-5.1.1/daemon/network.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/network.c 2020-12-09 09:44:29.000000000 +0000 @@ -1,4 +1,4 @@ -/* Copyright (C) 2015-2017 CZ.NIC, z.s.p.o. +/* Copyright (C) 2015-2020 CZ.NIC, z.s.p.o. * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -123,7 +124,8 @@ static void endpoint_close(struct network *net, struct endpoint *ep, bool force) { - bool control = ep->flags.kind && strcmp(ep->flags.kind, "control") == 0; + const bool is_control = ep->flags.kind && strcmp(ep->flags.kind, "control") == 0; + const bool is_xdp = ep->family == AF_XDP; if (ep->family == AF_UNIX) { /* The FS name would be left behind. */ /* Extract local address for this socket. */ @@ -138,7 +140,7 @@ } } - if (ep->flags.kind && !control) { + if (ep->flags.kind && !is_control && !is_xdp) { assert(!ep->handle); /* Special lua-handled endpoint. */ if (ep->engaged) { @@ -151,7 +153,7 @@ return; } - free_const(ep->flags.kind); /* needed if (control) */ + free_const(ep->flags.kind); /* needed if (is_control) */ assert(ep->handle); if (force) { /* Force close if event loop isn't running. */ if (ep->fd >= 0) { @@ -240,12 +242,18 @@ return kr_ok(); } -/** Open endpoint protocols. ep->flags were pre-set. */ -static int open_endpoint(struct network *net, struct endpoint *ep, - const struct sockaddr *sa, const char *log_addr) -{ - bool control = ep->flags.kind && strcmp(ep->flags.kind, "control") == 0; - if ((sa != NULL) == (ep->fd != -1)) { +/** Open endpoint protocols. ep->flags were pre-set. + * \p addr_str is only used for logging or for XDP "address". */ +static int open_endpoint(struct network *net, const char *addr_str, + struct endpoint *ep, const struct sockaddr *sa) +{ + const bool is_control = ep->flags.kind && strcmp(ep->flags.kind, "control") == 0; + const bool is_xdp = ep->family == AF_XDP; + bool ok = is_xdp + ? sa == NULL && ep->fd == -1 && ep->nic_queue >= 0 + && ep->flags.sock_type == SOCK_DGRAM && !ep->flags.tls + : (sa != NULL) != (ep->fd != -1); + if (!ok) { assert(!EINVAL); return kr_error(EINVAL); } @@ -265,33 +273,46 @@ ep->fd = io_bind(sa, ep->flags.sock_type, &ep->flags); if (ep->fd < 0) return ep->fd; } - if (ep->flags.kind && !control) { + if (ep->flags.kind && !is_control && !is_xdp) { /* This EP isn't to be managed internally after binding. */ - return endpoint_open_lua_cb(net, ep, log_addr); + return endpoint_open_lua_cb(net, ep, addr_str); } else { ep->engaged = true; - /* .engaged seems not really meaningful with .kind == NULL, but... */ + /* .engaged seems not really meaningful in this case, but... */ } - if (control) { + int ret; + if (is_control) { uv_pipe_t *ep_handle = malloc(sizeof(uv_pipe_t)); ep->handle = (uv_handle_t *)ep_handle; - if (!ep->handle) { - return kr_error(ENOMEM); - } - return io_listen_pipe(net->loop, ep_handle, ep->fd); + ret = !ep->handle ? ENOMEM + : io_listen_pipe(net->loop, ep_handle, ep->fd); + goto finish_ret; } if (ep->family == AF_UNIX) { /* Some parts of connection handling would need more work, * so let's support AF_UNIX only with .kind != NULL for now. */ kr_log_error("[system] AF_UNIX only supported with set { kind = '...' }\n"); - return kr_error(EAFNOSUPPORT); + ret = EAFNOSUPPORT; + goto finish_ret; /* uv_pipe_t *ep_handle = malloc(sizeof(uv_pipe_t)); */ } + if (is_xdp) { + #if ENABLE_XDP + uv_poll_t *ep_handle = malloc(sizeof(uv_poll_t)); + ep->handle = (uv_handle_t *)ep_handle; + ret = !ep->handle ? ENOMEM + : io_listen_xdp(net->loop, ep, addr_str); + #else + ret = ESOCKTNOSUPPORT; + #endif + goto finish_ret; + } /* else */ + if (ep->flags.sock_type == SOCK_DGRAM) { if (ep->flags.tls) { assert(!EINVAL); @@ -299,28 +320,33 @@ } uv_udp_t *ep_handle = malloc(sizeof(uv_udp_t)); ep->handle = (uv_handle_t *)ep_handle; - if (!ep->handle) { - return kr_error(ENOMEM); - } - return io_listen_udp(net->loop, ep_handle, ep->fd); + ret = !ep->handle ? ENOMEM + : io_listen_udp(net->loop, ep_handle, ep->fd); + goto finish_ret; } /* else */ if (ep->flags.sock_type == SOCK_STREAM) { uv_tcp_t *ep_handle = malloc(sizeof(uv_tcp_t)); ep->handle = (uv_handle_t *)ep_handle; - if (!ep->handle) { - return kr_error(ENOMEM); - } - return io_listen_tcp(net->loop, ep_handle, ep->fd, - net->tcp_backlog, ep->flags.tls); + ret = !ep->handle ? ENOMEM + : io_listen_tcp(net->loop, ep_handle, ep->fd, + net->tcp_backlog, ep->flags.tls, ep->flags.http); + goto finish_ret; } /* else */ assert(!EINVAL); return kr_error(EINVAL); +finish_ret: + if (!ret) return ret; + free(ep->handle); + ep->handle = NULL; + return kr_error(ret); } /** @internal Fetch a pointer to endpoint of given parameters (or NULL). - * Beware that there might be multiple matches, though that's not common. */ + * Beware that there might be multiple matches, though that's not common. + * The matching isn't really precise in the sense that it might not find + * and endpoint that would *collide* the passed one. */ static struct endpoint * endpoint_get(struct network *net, const char *addr, uint16_t port, endpoint_flags_t flags) { @@ -337,12 +363,13 @@ return NULL; } -/** \note pass either sa != NULL xor ep.fd != -1; +/** \note pass (either sa != NULL xor ep.fd != -1) or XDP case (neither sa nor ep.fd) + * \note in XDP case addr_str is interface name * \note ownership of ep.flags.* is taken on success. */ static int create_endpoint(struct network *net, const char *addr_str, struct endpoint *ep, const struct sockaddr *sa) { - int ret = open_endpoint(net, ep, sa, addr_str); + int ret = open_endpoint(net, addr_str, ep, sa); if (ret == 0) { ret = insert_endpoint(net, addr_str, ep); } @@ -354,6 +381,10 @@ int network_listen_fd(struct network *net, int fd, endpoint_flags_t flags) { + if (flags.xdp) { + assert(!EINVAL); + return kr_error(EINVAL); + } /* Extract fd's socket type. */ socklen_t len = sizeof(flags.sock_type); int ret = getsockopt(fd, SOL_SOCKET, SO_TYPE, &flags.sock_type, &len); @@ -410,29 +441,78 @@ return create_endpoint(net, addr_str, &ep, NULL); } +/** Try selecting XDP queue automatically. */ +static int16_t nic_queue_auto(void) +{ + const char *inst_str = getenv("SYSTEMD_INSTANCE"); + if (!inst_str) + return 0; // should work OK for simple (single-kresd) deployments + char *endp; + errno = 0; // strtol() is special in this respect + long inst = strtol(inst_str, &endp, 10); + if (!errno && *endp == '\0' && inst > 0 && inst < UINT16_MAX) + return inst - 1; // 1-based vs. 0-based indexing conventions + return -1; +} + int network_listen(struct network *net, const char *addr, uint16_t port, - endpoint_flags_t flags) + int16_t nic_queue, endpoint_flags_t flags) { - if (net == NULL || addr == 0 || port == 0) { + if (net == NULL || addr == 0 || nic_queue < -1) { assert(!EINVAL); return kr_error(EINVAL); } - if (endpoint_get(net, addr, port, flags)) { - return kr_error(EADDRINUSE); /* Already listening */ + + if (flags.xdp && nic_queue < 0) { + nic_queue = nic_queue_auto(); + if (nic_queue < 0) { + return kr_error(EINVAL); + } } - /* Parse address. */ + // Try parsing the address. const struct sockaddr *sa = kr_straddr_socket(addr, port, NULL); - if (!sa) { + if (!sa && !flags.xdp) { // unusable address spec return kr_error(EINVAL); } - struct endpoint ep = { - .flags = flags, - .fd = -1, - .port = port, - .family = sa->sa_family, - }; + char ifname_buf[64] UNUSED; + if (sa && flags.xdp) { // auto-detection: address -> interface + #if ENABLE_XDP + int ret = knot_eth_name_from_addr((const struct sockaddr_storage *)sa, + ifname_buf, sizeof(ifname_buf)); + // even on success we don't want to pass `sa` on + free_const(sa); + sa = NULL; + if (ret) { + return kr_error(ret); + } + addr = ifname_buf; + #else + return kr_error(ESOCKTNOSUPPORT); + #endif + } + // XDP: if addr failed to parse as address, we assume it's an interface name. + + if (endpoint_get(net, addr, port, flags)) { + return kr_error(EADDRINUSE); // Already listening + } + + struct endpoint ep = { 0 }; + ep.flags = flags; + ep.fd = -1; + ep.port = port; + ep.family = flags.xdp ? AF_XDP : sa->sa_family; + ep.nic_queue = nic_queue; + int ret = create_endpoint(net, addr, &ep, sa); + + // Error reporting: more precision. + if (ret == KNOT_EINVAL && !sa && flags.xdp && ENABLE_XDP) { + if (!if_nametoindex(addr) && errno == ENODEV) { + ret = kr_error(ENODEV); + } + } + free_const(sa); return ret; } diff -Nru knot-resolver-5.1.1/daemon/network.h knot-resolver-5.2.1/daemon/network.h --- knot-resolver-5.1.1/daemon/network.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/network.h 2020-12-09 09:44:29.000000000 +0000 @@ -1,4 +1,4 @@ -/* Copyright (C) 2015-2017 CZ.NIC, z.s.p.o. +/* Copyright (C) 2015-2020 CZ.NIC, z.s.p.o. * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -13,15 +13,22 @@ #include #include +#include +#ifndef AF_XDP +#define AF_XDP 44 +#endif struct engine; +struct session; -/** Ways to listen on a socket. */ +/** Ways to listen on a socket (which may exist already). */ typedef struct { int sock_type; /**< SOCK_DGRAM or SOCK_STREAM */ - bool tls; /**< only used together with .kind == NULL and .tcp */ - const char *kind; /**< tag for other types than the three usual */ - bool freebind; /**< used for binding to non-local address **/ + bool tls; /**< only used together with .kind == NULL and SOCK_STREAM */ + bool http; /**< DoH2, implies .tls (in current implementation) */ + bool xdp; /**< XDP is special (not a normal socket, in particular) */ + bool freebind; /**< used for binding to non-local address */ + const char *kind; /**< tag for other types: "control" or module-handled kinds */ } endpoint_flags_t; static inline bool endpoint_flags_eq(endpoint_flags_t f1, endpoint_flags_t f2) @@ -41,10 +48,13 @@ * ATM AF_UNIX is only supported with flags.kind != NULL */ struct endpoint { - uv_handle_t *handle; /**< uv_udp_t or uv_tcp_t; NULL in case flags.kind != NULL */ + /** uv_{udp,tcp,poll}_t (poll for XDP); + * NULL in case of endpoints that are to be handled by modules. */ + uv_handle_t *handle; int fd; /**< POSIX file-descriptor; always used. */ - int family; /**< AF_INET or AF_INET6 or AF_UNIX */ + int family; /**< AF_INET or AF_INET6 or AF_UNIX or AF_XDP */ uint16_t port; /**< TCP/UDP port. Meaningless with AF_UNIX. */ + int16_t nic_queue; /**< -1 or queue number of the interface for AF_XDP use. */ bool engaged; /**< to some module or internally */ endpoint_flags_t flags; }; @@ -88,9 +98,12 @@ * nothing is done and kr_error(EADDRINUSE) is returned. * \note there's no short-hand to listen both on UDP and TCP. * \note ownership of flags.* is taken on success. TODO: non-success? + * \param nic_queue == -1 for auto-selection or non-XDP. + * \note In XDP mode, addr may be also interface name, so kr_error(ENODEV) + * is returned if some nonsense is passed */ int network_listen(struct network *net, const char *addr, uint16_t port, - endpoint_flags_t flags); + int16_t nic_queue, endpoint_flags_t flags); /** Start listenting on an open file-descriptor. * \note flags.sock_type isn't meaningful here. diff -Nru knot-resolver-5.1.1/daemon/packaging/debian/10/builddeps knot-resolver-5.2.1/daemon/packaging/debian/10/builddeps --- knot-resolver-5.1.1/daemon/packaging/debian/10/builddeps 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/packaging/debian/10/builddeps 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -debhelper -libcmocka-dev -libedit-dev -libgnutls28-dev -libknot-dev -liblmdb-dev -luajit-5.1-dev -libsystemd-dev -libuv1-dev -luajit -pkg-config -meson -doxygen -python3-breathe -python3-sphinx -python3-sphinx-rtd-theme diff -Nru knot-resolver-5.1.1/daemon/packaging/debian/10/pre-build.sh knot-resolver-5.2.1/daemon/packaging/debian/10/pre-build.sh --- knot-resolver-5.1.1/daemon/packaging/debian/10/pre-build.sh 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/packaging/debian/10/pre-build.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later - -# add debian build repository -apt-get update -apt-get install -y wget gnupg apt-utils -echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-build/Debian_10/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-build.list -wget https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/Debian_Next/Release.key -O Release.key -apt-key add - < Release.key - -apt-get update -apt-get upgrade -y diff -Nru knot-resolver-5.1.1/daemon/packaging/debian/10/rundeps knot-resolver-5.2.1/daemon/packaging/debian/10/rundeps --- knot-resolver-5.1.1/daemon/packaging/debian/10/rundeps 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/packaging/debian/10/rundeps 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ -adduser -dns-root-data -systemd -libc6 -libdnssec7 -libedit2 -libgcc1 -libgnutls30 -libknot10 -liblmdb0 -libluajit-5.1-2 -libstdc++6 -libsystemd0 -libuv1 -libzscanner3 diff -Nru knot-resolver-5.1.1/daemon/packaging/test.config knot-resolver-5.2.1/daemon/packaging/test.config --- knot-resolver-5.1.1/daemon/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -quit() diff -Nru knot-resolver-5.1.1/daemon/.packaging/centos/7/builddeps knot-resolver-5.2.1/daemon/.packaging/centos/7/builddeps --- knot-resolver-5.1.1/daemon/.packaging/centos/7/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/centos/7/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,13 @@ +gcc +gcc-c++ +gnutls +knot-libs +knot-devel +libcmocka-devel +libedit-devel +libcap-ng +libuv-devel +lmdb-devel +luajit-devel +meson +systemd-devel diff -Nru knot-resolver-5.1.1/daemon/.packaging/centos/7/pre-build.sh knot-resolver-5.2.1/daemon/.packaging/centos/7/pre-build.sh --- knot-resolver-5.1.1/daemon/.packaging/centos/7/pre-build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/centos/7/pre-build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +yum update -y +yum install -y wget epel-release + +# add build repository +cd /etc/yum.repos.d/ +wget https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/CentOS_7_EPEL/home:CZ-NIC:knot-resolver-build.repo + diff -Nru knot-resolver-5.1.1/daemon/.packaging/centos/7/pre-run.sh knot-resolver-5.2.1/daemon/.packaging/centos/7/pre-run.sh --- knot-resolver-5.1.1/daemon/.packaging/centos/7/pre-run.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/centos/7/pre-run.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +yum update -y +yum install -y wget epel-release + +# add build repository +cd /etc/yum.repos.d/ +wget https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/CentOS_7_EPEL/home:CZ-NIC:knot-resolver-latest.repo diff -Nru knot-resolver-5.1.1/daemon/.packaging/centos/7/rundeps knot-resolver-5.2.1/daemon/.packaging/centos/7/rundeps --- knot-resolver-5.1.1/daemon/.packaging/centos/7/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/centos/7/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,6 @@ +knot-libs +libedit +libuv +luajit +lua-basexx +lua-http diff -Nru knot-resolver-5.1.1/daemon/.packaging/centos/8/builddeps knot-resolver-5.2.1/daemon/.packaging/centos/8/builddeps --- knot-resolver-5.1.1/daemon/.packaging/centos/8/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/centos/8/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,14 @@ +gcc +gcc-c++ +meson +"pkgconfig(cmocka)" +"pkgconfig(gnutls)" +"pkgconfig(libedit)" +"pkgconfig(libknot)" +"pkgconfig(libzscanner)" +"pkgconfig(libdnssec)" +"pkgconfig(libsystemd)" +"pkgconfig(libcap-ng)" +"pkgconfig(libuv)" +"pkgconfig(lmdb)" +"pkgconfig(luajit)" diff -Nru knot-resolver-5.1.1/daemon/.packaging/centos/8/pre-build.sh knot-resolver-5.2.1/daemon/.packaging/centos/8/pre-build.sh --- knot-resolver-5.1.1/daemon/.packaging/centos/8/pre-build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/centos/8/pre-build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +dnf install -y wget 'dnf-command(config-manager)' epel-release centos-release + +dnf config-manager --enable PowerTools +dnf config-manager --enable Devel +dnf config-manager --add-repo https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/CentOS_8_EPEL/home:CZ-NIC:knot-resolver-build.repo +dnf install -y knot +dnf upgrade -y diff -Nru knot-resolver-5.1.1/daemon/.packaging/centos/8/pre-run.sh knot-resolver-5.2.1/daemon/.packaging/centos/8/pre-run.sh --- knot-resolver-5.1.1/daemon/.packaging/centos/8/pre-run.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/centos/8/pre-run.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +dnf install -y wget 'dnf-command(config-manager)' epel-release centos-release + +dnf config-manager --enable PowerTools +dnf config-manager --add-repo https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/CentOS_8_EPEL/home:CZ-NIC:knot-resolver-latest.repo +dnf upgrade -y diff -Nru knot-resolver-5.1.1/daemon/.packaging/centos/8/rundeps knot-resolver-5.2.1/daemon/.packaging/centos/8/rundeps --- knot-resolver-5.1.1/daemon/.packaging/centos/8/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/centos/8/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,6 @@ +libedit +knot-libs +libuv +luajit +lua5.1-basexx +lua5.1-http diff -Nru knot-resolver-5.1.1/daemon/.packaging/debian/10/builddeps knot-resolver-5.2.1/daemon/.packaging/debian/10/builddeps --- knot-resolver-5.1.1/daemon/.packaging/debian/10/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/debian/10/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,12 @@ +debhelper +libcmocka-dev +libedit-dev +libgnutls28-dev +libknot-dev +liblmdb-dev +luajit-5.1-dev +libsystemd-dev +libuv1-dev +luajit +pkg-config +meson diff -Nru knot-resolver-5.1.1/daemon/.packaging/debian/10/pre-build.sh knot-resolver-5.2.1/daemon/.packaging/debian/10/pre-build.sh --- knot-resolver-5.1.1/daemon/.packaging/debian/10/pre-build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/debian/10/pre-build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# add debian build repository +apt-get update +apt-get install -y wget gnupg apt-utils +echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-build/Debian_10/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-build.list +wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/Debian_10/Release.key -O Release.key +apt-key add - < Release.key + +apt-get update +apt-get upgrade -y diff -Nru knot-resolver-5.1.1/daemon/.packaging/debian/10/pre-run.sh knot-resolver-5.2.1/daemon/.packaging/debian/10/pre-run.sh --- knot-resolver-5.1.1/daemon/.packaging/debian/10/pre-run.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/debian/10/pre-run.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +apt-get update +apt-get install -y wget gnupg apt-utils + +echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-latest/Debian_10/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-latest.list +wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/Debian_10/Release.key -O Release.key +apt-key add - < Release.key + +apt-get update +apt-get upgrade -y diff -Nru knot-resolver-5.1.1/daemon/.packaging/debian/10/rundeps knot-resolver-5.2.1/daemon/.packaging/debian/10/rundeps --- knot-resolver-5.1.1/daemon/.packaging/debian/10/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/debian/10/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,15 @@ +adduser +dns-root-data +systemd +libc6 +libdnssec7 +libedit2 +libgcc1 +libgnutls30 +libknot10 +liblmdb0 +libluajit-5.1-2 +libstdc++6 +libsystemd0 +libuv1 +libzscanner3 diff -Nru knot-resolver-5.1.1/daemon/.packaging/debian/9/builddeps knot-resolver-5.2.1/daemon/.packaging/debian/9/builddeps --- knot-resolver-5.1.1/daemon/.packaging/debian/9/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/debian/9/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,12 @@ +debhelper +libcmocka-dev +libedit-dev +libgnutls28-dev +libknot-dev +liblmdb-dev +luajit-5.1-dev +libsystemd-dev +libuv1-dev +luajit +pkg-config +meson diff -Nru knot-resolver-5.1.1/daemon/.packaging/debian/9/pre-build.sh knot-resolver-5.2.1/daemon/.packaging/debian/9/pre-build.sh --- knot-resolver-5.1.1/daemon/.packaging/debian/9/pre-build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/debian/9/pre-build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# add debian build repository +apt-get update +apt-get install -y wget gnupg apt-utils +echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-build/Debian_9.0/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-build.list +wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/Debian_9.0/Release.key -O Release.key +apt-key add - < Release.key + +apt-get update +apt-get upgrade -y diff -Nru knot-resolver-5.1.1/daemon/.packaging/debian/9/pre-run.sh knot-resolver-5.2.1/daemon/.packaging/debian/9/pre-run.sh --- knot-resolver-5.1.1/daemon/.packaging/debian/9/pre-run.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/debian/9/pre-run.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +apt-get update +apt-get install -y wget gnupg apt-utils + +echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-latest/Debian_9.0/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-latest.list +wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/Debian_9.0/Release.key -O Release.key +apt-key add - < Release.key + +apt-get update +apt-get upgrade -y diff -Nru knot-resolver-5.1.1/daemon/.packaging/debian/9/rundeps knot-resolver-5.2.1/daemon/.packaging/debian/9/rundeps --- knot-resolver-5.1.1/daemon/.packaging/debian/9/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/debian/9/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,15 @@ +adduser +dns-root-data +systemd +libc6 +libdnssec7 +libedit2 +libgcc1 +libgnutls30 +libknot10 +liblmdb0 +libluajit-5.1-2 +libstdc++6 +libsystemd0 +libuv1 +libzscanner3 diff -Nru knot-resolver-5.1.1/daemon/.packaging/fedora/31/builddeps knot-resolver-5.2.1/daemon/.packaging/fedora/31/builddeps --- knot-resolver-5.1.1/daemon/.packaging/fedora/31/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/fedora/31/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,14 @@ +gcc +gcc-c++ +meson +"pkgconfig(cmocka)" +"pkgconfig(gnutls)" +"pkgconfig(libedit)" +"pkgconfig(libknot)" +"pkgconfig(libzscanner)" +"pkgconfig(libdnssec)" +"pkgconfig(libsystemd)" +"pkgconfig(libcap-ng)" +"pkgconfig(libuv)" +"pkgconfig(lmdb)" +"pkgconfig(luajit)" diff -Nru knot-resolver-5.1.1/daemon/.packaging/fedora/31/pre-build.sh knot-resolver-5.2.1/daemon/.packaging/fedora/31/pre-build.sh --- knot-resolver-5.1.1/daemon/.packaging/fedora/31/pre-build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/fedora/31/pre-build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +dnf install -y wget + +dnf config-manager --add-repo https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/Fedora_31/home:CZ-NIC:knot-resolver-build.repo +dnf install -y knot +dnf upgrade -y diff -Nru knot-resolver-5.1.1/daemon/.packaging/fedora/31/pre-run.sh knot-resolver-5.2.1/daemon/.packaging/fedora/31/pre-run.sh --- knot-resolver-5.1.1/daemon/.packaging/fedora/31/pre-run.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/fedora/31/pre-run.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +dnf install -y wget + +dnf config-manager --add-repo https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/Fedora_31/home:CZ-NIC:knot-resolver-latest.repo +dnf upgrade -y diff -Nru knot-resolver-5.1.1/daemon/.packaging/fedora/31/rundeps knot-resolver-5.2.1/daemon/.packaging/fedora/31/rundeps --- knot-resolver-5.1.1/daemon/.packaging/fedora/31/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/fedora/31/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,7 @@ +libedit +knot-libs +libuv +luajit +lua5.1-basexx +lua5.1-psl +lua5.1-http diff -Nru knot-resolver-5.1.1/daemon/.packaging/fedora/32/builddeps knot-resolver-5.2.1/daemon/.packaging/fedora/32/builddeps --- knot-resolver-5.1.1/daemon/.packaging/fedora/32/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/fedora/32/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,14 @@ +gcc +gcc-c++ +meson +"pkgconfig(cmocka)" +"pkgconfig(gnutls)" +"pkgconfig(libedit)" +"pkgconfig(libknot)" +"pkgconfig(libzscanner)" +"pkgconfig(libdnssec)" +"pkgconfig(libsystemd)" +"pkgconfig(libcap-ng)" +"pkgconfig(libuv)" +"pkgconfig(lmdb)" +"pkgconfig(luajit)" diff -Nru knot-resolver-5.1.1/daemon/.packaging/fedora/32/pre-build.sh knot-resolver-5.2.1/daemon/.packaging/fedora/32/pre-build.sh --- knot-resolver-5.1.1/daemon/.packaging/fedora/32/pre-build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/fedora/32/pre-build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +dnf install -y wget + +dnf config-manager --add-repo https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/Fedora_32/home:CZ-NIC:knot-resolver-build.repo +dnf install -y knot +dnf upgrade -y diff -Nru knot-resolver-5.1.1/daemon/.packaging/fedora/32/pre-run.sh knot-resolver-5.2.1/daemon/.packaging/fedora/32/pre-run.sh --- knot-resolver-5.1.1/daemon/.packaging/fedora/32/pre-run.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/fedora/32/pre-run.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +dnf install -y wget + +dnf config-manager --add-repo https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/Fedora_32/home:CZ-NIC:knot-resolver-latest.repo +dnf upgrade -y diff -Nru knot-resolver-5.1.1/daemon/.packaging/fedora/32/rundeps knot-resolver-5.2.1/daemon/.packaging/fedora/32/rundeps --- knot-resolver-5.1.1/daemon/.packaging/fedora/32/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/fedora/32/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,7 @@ +libedit +knot-libs +libuv +luajit +lua5.1-basexx +lua5.1-psl +lua5.1-http diff -Nru knot-resolver-5.1.1/daemon/.packaging/leap/15.2/builddeps knot-resolver-5.2.1/daemon/.packaging/leap/15.2/builddeps --- knot-resolver-5.1.1/daemon/.packaging/leap/15.2/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/leap/15.2/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,14 @@ +gcc +gcc-c++ +lmdb-devel +meson +"pkgconfig(cmocka)" +"pkgconfig(gnutls)" +"pkgconfig(libedit)" +"pkgconfig(libknot)" +"pkgconfig(libzscanner)" +"pkgconfig(libdnssec)" +"pkgconfig(libsystemd)" +"pkgconfig(libcap-ng)" +"pkgconfig(libuv)" +"pkgconfig(luajit)" diff -Nru knot-resolver-5.1.1/daemon/.packaging/leap/15.2/pre-build.sh knot-resolver-5.2.1/daemon/.packaging/leap/15.2/pre-build.sh --- knot-resolver-5.1.1/daemon/.packaging/leap/15.2/pre-build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/leap/15.2/pre-build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +zypper addrepo https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/openSUSE_Leap_15.2/home:CZ-NIC:knot-resolver-build.repo +zypper --no-gpg-checks refresh +zypper install -y knot + diff -Nru knot-resolver-5.1.1/daemon/.packaging/leap/15.2/pre-run.sh knot-resolver-5.2.1/daemon/.packaging/leap/15.2/pre-run.sh --- knot-resolver-5.1.1/daemon/.packaging/leap/15.2/pre-run.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/leap/15.2/pre-run.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +zypper addrepo https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/openSUSE_Leap_15.2/home:CZ-NIC:knot-resolver-latest.repo +zypper --no-gpg-checks refresh diff -Nru knot-resolver-5.1.1/daemon/.packaging/leap/15.2/rundeps knot-resolver-5.2.1/daemon/.packaging/leap/15.2/rundeps --- knot-resolver-5.1.1/daemon/.packaging/leap/15.2/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/leap/15.2/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +libedit0 +knot-libs +libuv1 +libluajit-5_1-2 diff -Nru knot-resolver-5.1.1/daemon/.packaging/leap/docker-image-name knot-resolver-5.2.1/daemon/.packaging/leap/docker-image-name --- knot-resolver-5.1.1/daemon/.packaging/leap/docker-image-name 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/leap/docker-image-name 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +opensuse/leap diff -Nru knot-resolver-5.1.1/daemon/.packaging/test.config knot-resolver-5.2.1/daemon/.packaging/test.config --- knot-resolver-5.1.1/daemon/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,2 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +quit() diff -Nru knot-resolver-5.1.1/daemon/.packaging/ubuntu/16.04/builddeps knot-resolver-5.2.1/daemon/.packaging/ubuntu/16.04/builddeps --- knot-resolver-5.1.1/daemon/.packaging/ubuntu/16.04/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/ubuntu/16.04/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,16 @@ +debhelper +libcmocka-dev +libedit-dev +libgnutls28-dev +libknot-dev +liblmdb-dev +libluajit-5.1-dev +libsystemd-dev +libuv1-dev +luajit +pkg-config +meson +doxygen +python3-breathe +python3-sphinx +python3-sphinx-rtd-theme diff -Nru knot-resolver-5.1.1/daemon/.packaging/ubuntu/16.04/pre-build.sh knot-resolver-5.2.1/daemon/.packaging/ubuntu/16.04/pre-build.sh --- knot-resolver-5.1.1/daemon/.packaging/ubuntu/16.04/pre-build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/ubuntu/16.04/pre-build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# add build repository +apt-get update +apt-get install -y wget gnupg apt-utils + +echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-build/xUbuntu_16.04/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-build.list +wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/xUbuntu_16.04/Release.key -O Release.key +apt-key add - < Release.key + +apt-get update +apt-get upgrade -y diff -Nru knot-resolver-5.1.1/daemon/.packaging/ubuntu/16.04/pre-run.sh knot-resolver-5.2.1/daemon/.packaging/ubuntu/16.04/pre-run.sh --- knot-resolver-5.1.1/daemon/.packaging/ubuntu/16.04/pre-run.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/ubuntu/16.04/pre-run.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# add build repository +apt-get update +apt-get install -y wget gnupg apt-utils + +echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-latest/xUbuntu_16.04/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-latest.list +wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/xUbuntu_16.04/Release.key -O Release.key +apt-key add - < Release.key + +apt-get update +apt-get upgrade -y diff -Nru knot-resolver-5.1.1/daemon/.packaging/ubuntu/16.04/rundeps knot-resolver-5.2.1/daemon/.packaging/ubuntu/16.04/rundeps --- knot-resolver-5.1.1/daemon/.packaging/ubuntu/16.04/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/ubuntu/16.04/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,15 @@ +adduser +dns-root-data +systemd +libc6 +libdnssec7 +libedit2 +libgcc1 +libgnutls30 +libknot10 +liblmdb0 +libluajit-5.1-2 +libstdc++6 +libsystemd0 +libuv1 +libzscanner3 diff -Nru knot-resolver-5.1.1/daemon/.packaging/ubuntu/18.04/builddeps knot-resolver-5.2.1/daemon/.packaging/ubuntu/18.04/builddeps --- knot-resolver-5.1.1/daemon/.packaging/ubuntu/18.04/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/ubuntu/18.04/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,16 @@ +debhelper +libcmocka-dev +libedit-dev +libgnutls28-dev +libknot-dev +liblmdb-dev +libluajit-5.1-dev +libsystemd-dev +libuv1-dev +luajit +pkg-config +meson +doxygen +python3-breathe +python3-sphinx +python3-sphinx-rtd-theme diff -Nru knot-resolver-5.1.1/daemon/.packaging/ubuntu/18.04/pre-build.sh knot-resolver-5.2.1/daemon/.packaging/ubuntu/18.04/pre-build.sh --- knot-resolver-5.1.1/daemon/.packaging/ubuntu/18.04/pre-build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/ubuntu/18.04/pre-build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# add build repository +apt-get update +apt-get install -y wget gnupg apt-utils + +echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-build/xUbuntu_18.04/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-build.list +wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/xUbuntu_18.04/Release.key -O Release.key +apt-key add - < Release.key + +apt-get update +apt-get upgrade -y diff -Nru knot-resolver-5.1.1/daemon/.packaging/ubuntu/18.04/pre-run.sh knot-resolver-5.2.1/daemon/.packaging/ubuntu/18.04/pre-run.sh --- knot-resolver-5.1.1/daemon/.packaging/ubuntu/18.04/pre-run.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/ubuntu/18.04/pre-run.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# add build repository +apt-get update +apt-get install -y wget gnupg apt-utils + +echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-latest/xUbuntu_18.04/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-latest.list +wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/xUbuntu_18.04/Release.key -O Release.key +apt-key add - < Release.key + +apt-get update +apt-get upgrade -y diff -Nru knot-resolver-5.1.1/daemon/.packaging/ubuntu/18.04/rundeps knot-resolver-5.2.1/daemon/.packaging/ubuntu/18.04/rundeps --- knot-resolver-5.1.1/daemon/.packaging/ubuntu/18.04/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/ubuntu/18.04/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,15 @@ +adduser +dns-root-data +systemd +libc6 +libdnssec7 +libedit2 +libgcc1 +libgnutls30 +libknot10 +liblmdb0 +libluajit-5.1-2 +libstdc++6 +libsystemd0 +libuv1 +libzscanner3 diff -Nru knot-resolver-5.1.1/daemon/.packaging/ubuntu/20.04/builddeps knot-resolver-5.2.1/daemon/.packaging/ubuntu/20.04/builddeps --- knot-resolver-5.1.1/daemon/.packaging/ubuntu/20.04/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/ubuntu/20.04/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,16 @@ +debhelper +libcmocka-dev +libedit-dev +libgnutls28-dev +libknot-dev +liblmdb-dev +libluajit-5.1-dev +libsystemd-dev +libuv1-dev +luajit +pkg-config +meson +doxygen +python3-breathe +python3-sphinx +python3-sphinx-rtd-theme diff -Nru knot-resolver-5.1.1/daemon/.packaging/ubuntu/20.04/pre-build.sh knot-resolver-5.2.1/daemon/.packaging/ubuntu/20.04/pre-build.sh --- knot-resolver-5.1.1/daemon/.packaging/ubuntu/20.04/pre-build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/ubuntu/20.04/pre-build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# add build repository +apt-get update +apt-get install -y wget gnupg apt-utils + +echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-build/xUbuntu_20.04/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-build.list +wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/xUbuntu_20.04/Release.key -O Release.key +apt-key add - < Release.key + +apt-get update +apt-get upgrade -y diff -Nru knot-resolver-5.1.1/daemon/.packaging/ubuntu/20.04/pre-run.sh knot-resolver-5.2.1/daemon/.packaging/ubuntu/20.04/pre-run.sh --- knot-resolver-5.1.1/daemon/.packaging/ubuntu/20.04/pre-run.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/ubuntu/20.04/pre-run.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# add build repository +apt-get update +apt-get install -y wget gnupg apt-utils + +echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-latest/xUbuntu_20.04/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-latest.list +wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/xUbuntu_20.04/Release.key -O Release.key +apt-key add - < Release.key + +apt-get update +apt-get upgrade -y diff -Nru knot-resolver-5.1.1/daemon/.packaging/ubuntu/20.04/rundeps knot-resolver-5.2.1/daemon/.packaging/ubuntu/20.04/rundeps --- knot-resolver-5.1.1/daemon/.packaging/ubuntu/20.04/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/daemon/.packaging/ubuntu/20.04/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,15 @@ +adduser +dns-root-data +systemd +libc6 +libdnssec7 +libedit2 +libgcc1 +libgnutls30 +libknot10 +liblmdb0 +libluajit-5.1-2 +libstdc++6 +libsystemd0 +libuv1 +libzscanner3 diff -Nru knot-resolver-5.1.1/daemon/scripting.rst knot-resolver-5.2.1/daemon/scripting.rst --- knot-resolver-5.1.1/daemon/scripting.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/scripting.rst 2020-12-09 09:44:29.000000000 +0000 @@ -22,8 +22,8 @@ --------------- Control socket acts like "an interactive configuration file" so all actions available in configuration file can be executed interactively using the control -socket. One possible use-case is reconfiguring Resolver instances from another -program, e.g. a maintenance script. +socket. One possible use-case is reconfiguring the resolver instances from +another program, e.g. a maintenance script. .. note:: Each instance of Knot Resolver exposes its own control socket. Take that into account when scripting deployments with @@ -32,12 +32,10 @@ When Knot Resolver is started using Systemd (see section :ref:`quickstart-startup`) it creates a control socket in path ``/run/knot-resolver/control/$ID``. Connection to the socket can -be made from command line using e.g. ``netcat`` or ``socat``: +be made from command line using e.g. ``socat``: .. code-block:: bash - $ nc -U /run/knot-resolver/control/1 - or $ socat - UNIX-CONNECT:/run/knot-resolver/control/1 When successfully connected to a socket, the command line should change to @@ -61,6 +59,78 @@ list of sockets corresponds to the list of processes, and you can test the process for liveliness by connecting to the UNIX socket. +.. function:: map(lua_snippet) + + Executes the provided string as lua code on every running resolver instance + and returns the results as a table. + + Key ``n`` is always present in the returned table and specifies the total + number of instances the command was executed on. The table also contains + results from each instance accessible through keys ``1`` to ``n`` + (inclusive). If any instance returns ``nil``, it is not explicitly part of + the table, but you can detect it by iterating through ``1`` to ``n``. + + .. code-block:: lua + + > map('worker.id') -- return an ID of every active instance + { + '2', + '1', + ['n'] = 2, + } + > map('worker.id == "1" or nil') -- example of `nil` return value + { + [2] = true, + ['n'] = 2, + } + + The order of instances isn't guaranteed or stable. When you need to identify + the instances, you may use ``kluautil.kr_table_pack()`` function to return multiple + values as a table. It uses similar semantics with ``n`` as described above + to allow ``nil`` values. + + .. code-block:: lua + + > map('require("kluautil").kr_table_pack(worker.id, stats.get("answer.total"))') + { + { + '2', + 42, + ['n'] = 2, + }, + { + '1', + 69, + ['n'] = 2, + }, + ['n'] = 2, + } + + If the command fails on any instance, an error is returned and the execution + is in an undefined state (the command might not have been executed on all + instances). When using the ``map()`` function to execute any code that might + fail, your code should be wrapped in `pcall() + `_ to avoid this + issue. + + .. code-block:: lua + + > map('require("kluautil").kr_table_pack(pcall(net.tls, "cert.pem", "key.pem"))') + { + { + true, -- function suceeded + true, -- function return value(s) + ['n'] = 2, + }, + { + false, -- function failed + 'error occurred...', -- the returned error message + ['n'] = 2, + }, + ['n'] = 2, + } + + Lua scripts ----------- diff -Nru knot-resolver-5.1.1/daemon/session.c knot-resolver-5.2.1/daemon/session.c --- knot-resolver-5.1.1/daemon/session.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/session.c 2020-12-09 09:44:29.000000000 +0000 @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 CZ.NIC, z.s.p.o. +/* Copyright (C) 2018-2020 CZ.NIC, z.s.p.o. * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -10,17 +10,23 @@ #include "daemon/session.h" #include "daemon/engine.h" #include "daemon/tls.h" +#include "daemon/http.h" #include "daemon/worker.h" #include "daemon/io.h" #include "lib/generic/queue.h" #define TLS_CHUNK_SIZE (16 * 1024) +/* Initial max frame size: https://tools.ietf.org/html/rfc7540#section-6.5.2 */ +#define HTTP_MAX_FRAME_SIZE 16384 + /* Per-socket (TCP or UDP) persistent structure. * * In particular note that for UDP clients it's just one session (per socket) - * shared for all clients. For TCP/TLS it's for the connection-specific socket, + * shared for all clients. For TCP/TLS it's also for the connection-specific socket, * i.e one session per connection. + * + * LATER(optim.): the memory here is used a bit wastefully. */ struct session { struct session_flags sflags; /**< miscellaneous flags. */ @@ -29,13 +35,17 @@ uv_handle_t *handle; /**< libuv handle for IO operations. */ uv_timer_t timeout; /**< libuv handle for timer. */ - struct tls_ctx_t *tls_ctx; /**< server side tls-related data. */ - struct tls_client_ctx_t *tls_client_ctx; /**< client side tls-related data. */ + struct tls_ctx *tls_ctx; /**< server side tls-related data. */ + struct tls_client_ctx *tls_client_ctx; /**< client side tls-related data. */ + +#if ENABLE_DOH2 + struct http_ctx *http_ctx; /**< server side http-related data. */ +#endif trie_t *tasks; /**< list of tasks assotiated with given session. */ queue_t(struct qr_task *) waiting; /**< list of tasks waiting for sending to upstream. */ - uint8_t *wire_buf; /**< Buffer for DNS message. */ + uint8_t *wire_buf; /**< Buffer for DNS message, except for XDP. */ ssize_t wire_buf_size; /**< Buffer size. */ ssize_t wire_buf_start_idx; /**< Data start offset in wire_buf. */ ssize_t wire_buf_end_idx; /**< Data end offset in wire_buf. */ @@ -81,6 +91,9 @@ queue_deinit(session->waiting); tls_free(session->tls_ctx); tls_client_ctx_free(session->tls_client_ctx); +#if ENABLE_DOH2 + http_free(session->http_ctx); +#endif memset(session, 0, sizeof(*session)); } @@ -257,22 +270,22 @@ return &session->sockname.ip; } -struct tls_ctx_t *session_tls_get_server_ctx(const struct session *session) +struct tls_ctx *session_tls_get_server_ctx(const struct session *session) { return session->tls_ctx; } -void session_tls_set_server_ctx(struct session *session, struct tls_ctx_t *ctx) +void session_tls_set_server_ctx(struct session *session, struct tls_ctx *ctx) { session->tls_ctx = ctx; } -struct tls_client_ctx_t *session_tls_get_client_ctx(const struct session *session) +struct tls_client_ctx *session_tls_get_client_ctx(const struct session *session) { return session->tls_client_ctx; } -void session_tls_set_client_ctx(struct session *session, struct tls_client_ctx_t *ctx) +void session_tls_set_client_ctx(struct session *session, struct tls_client_ctx *ctx) { session->tls_client_ctx = ctx; } @@ -284,6 +297,18 @@ return tls_ctx; } +#if ENABLE_DOH2 +struct http_ctx *session_http_get_server_ctx(const struct session *session) +{ + return session->http_ctx; +} + +void session_http_set_server_ctx(struct session *session, struct http_ctx *ctx) +{ + session->http_ctx = ctx; +} +#endif + uv_handle_t *session_get_handle(struct session *session) { return session->handle; @@ -294,7 +319,7 @@ return h->data; } -struct session *session_new(uv_handle_t *handle, bool has_tls) +struct session *session_new(uv_handle_t *handle, bool has_tls, bool has_http) { if (!handle) { return NULL; @@ -314,6 +339,14 @@ wire_buffer_size += TLS_CHUNK_SIZE; session->sflags.has_tls = true; } +#if ENABLE_DOH2 + if (has_http) { + /* When decoding large packets, + * HTTP/2 frames can be up to 16 KB by default. */ + wire_buffer_size += HTTP_MAX_FRAME_SIZE; + session->sflags.has_http = true; + } +#endif uint8_t *wire_buf = malloc(wire_buffer_size); if (!wire_buf) { free(session); @@ -331,9 +364,13 @@ * We still need to keep in mind to only touch the buffer * in this callback... */ assert(handle->loop->data); - struct worker_ctx *worker = handle->loop->data; - session->wire_buf = worker->wire_buf; - session->wire_buf_size = sizeof(worker->wire_buf); + session->wire_buf = the_worker->wire_buf; + session->wire_buf_size = sizeof(the_worker->wire_buf); + } else { + assert(handle->type == UV_POLL/*XDP*/); + /* - wire_buf* are left zeroed, as they make no sense + * - timer is unused but OK for simplicity (server-side sessions are few) + */ } uv_timer_init(handle->loop, &session->timeout); @@ -522,7 +559,7 @@ session->wire_buf_end_idx = 0; return NULL; } - + if (session->wire_buf_start_idx > session->wire_buf_end_idx) { session->sflags.wirebuf_error = true; session->wire_buf_start_idx = 0; @@ -534,7 +571,7 @@ uint8_t *msg_start = &session->wire_buf[session->wire_buf_start_idx]; ssize_t wirebuf_msg_data_size = session->wire_buf_end_idx - session->wire_buf_start_idx; uint16_t msg_size = 0; - + if (!handle) { session->sflags.wirebuf_error = true; return NULL; @@ -637,7 +674,7 @@ session->wire_buf_start_idx += pkt_msg_size; } session->sflags.wirebuf_error = false; - + wirebuf_data_size = session->wire_buf_end_idx - session->wire_buf_start_idx; if (wirebuf_data_size == 0) { session_wirebuf_discard(session); @@ -708,57 +745,62 @@ int session_wirebuf_process(struct session *session, const struct sockaddr *peer) { int ret = 0; - if (session->wire_buf_start_idx == session->wire_buf_end_idx) { + if (session->wire_buf_start_idx == session->wire_buf_end_idx) return ret; - } - struct worker_ctx *worker = session_get_handle(session)->loop->data; + size_t wirebuf_data_size = session->wire_buf_end_idx - session->wire_buf_start_idx; - uint32_t max_iterations = (wirebuf_data_size / (KNOT_WIRE_HEADER_SIZE + KNOT_WIRE_QUESTION_MIN_SIZE)) + 1; - knot_pkt_t *query = NULL; - while (((query = session_produce_packet(session, &worker->pkt_pool)) != NULL) && + uint32_t max_iterations = (wirebuf_data_size / + (KNOT_WIRE_HEADER_SIZE + KNOT_WIRE_QUESTION_MIN_SIZE)) + 1; + knot_pkt_t *pkt = NULL; + + while (((pkt = session_produce_packet(session, &the_worker->pkt_pool)) != NULL) && (ret < max_iterations)) { assert (!session_wirebuf_error(session)); - int res = worker_submit(session, peer, query); - if (res != kr_error(EILSEQ)) { - /* Packet has been successfully parsed. */ + int res = worker_submit(session, peer, NULL, NULL, NULL, pkt); + /* Errors from worker_submit() are intetionally *not* handled in order to + * ensure the entire wire buffer is processed. */ + if (res == kr_ok()) ret += 1; - } - if (session_discard_packet(session, query) < 0) { + if (session_discard_packet(session, pkt) < 0) { /* Packet data isn't stored in memory as expected. - something went wrong, normally should not happen. */ + * something went wrong, normally should not happen. */ break; } } - if (session_wirebuf_error(session)) { + + /* worker_submit() may cause the session to close (e.g. due to IO + * write error when the packet triggers an immediate answer). This is + * an error state, as well as any wirebuf error. */ + if (session->sflags.closing || session_wirebuf_error(session)) ret = -1; - } + return ret; } -void session_kill_ioreq(struct session *s, struct qr_task *task) +void session_kill_ioreq(struct session *session, struct qr_task *task) { - if (!s) { + if (!session) { return; } - assert(s->sflags.outgoing && s->handle); - if (s->sflags.closing) { + assert(session->sflags.outgoing && session->handle); + if (session->sflags.closing) { return; } - session_tasklist_del(s, task); - if (s->handle->type == UV_UDP) { - assert(session_tasklist_is_empty(s)); - session_close(s); + session_tasklist_del(session, task); + if (session->handle->type == UV_UDP) { + assert(session_tasklist_is_empty(session)); + session_close(session); return; } } /** Update timestamp */ -void session_touch(struct session *s) +void session_touch(struct session *session) { - s->last_activity = kr_now(); + session->last_activity = kr_now(); } -uint64_t session_last_activity(struct session *s) +uint64_t session_last_activity(struct session *session) { - return s->last_activity; + return session->last_activity; } diff -Nru knot-resolver-5.1.1/daemon/session.h knot-resolver-5.2.1/daemon/session.h --- knot-resolver-5.1.1/daemon/session.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/session.h 2020-12-09 09:44:29.000000000 +0000 @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 CZ.NIC, z.s.p.o. +/* Copyright (C) 2018-2020 CZ.NIC, z.s.p.o. * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -17,17 +17,18 @@ bool outgoing : 1; /**< True: to upstream; false: from a client. */ bool throttled : 1; /**< True: data reading from peer is temporarily stopped. */ bool has_tls : 1; /**< True: given session uses TLS. */ + bool has_http : 1; /**< True: given session uses HTTP. */ bool connected : 1; /**< True: TCP connection is established. */ bool closing : 1; /**< True: session close sequence is in progress. */ bool wirebuf_error : 1; /**< True: last operation with wirebuf ended up with an error. */ }; -/* Allocate new session for a libuv handle. - * If handle->tyoe is UV_UDP, tls parameter will be ignored. */ -struct session *session_new(uv_handle_t *handle, bool has_tls); -/* Clear and free given session. */ +/** Allocate new session for a libuv handle. + * If handle->type isn't UV_TCP, has_* parameters will be ignored. */ +struct session *session_new(uv_handle_t *handle, bool has_tls, bool has_http); +/** Clear and free given session. */ void session_free(struct session *session); -/* Clear session. */ +/** Clear session. */ void session_clear(struct session *session); /** Close session. */ void session_close(struct session *session); @@ -84,17 +85,24 @@ /** Get pointer to sockname (address of our end, not meaningful for UDP downstream). */ struct sockaddr *session_get_sockname(struct session *session); /** Get pointer to server-side tls-related data. */ -struct tls_ctx_t *session_tls_get_server_ctx(const struct session *session); +struct tls_ctx *session_tls_get_server_ctx(const struct session *session); /** Set pointer to server-side tls-related data. */ -void session_tls_set_server_ctx(struct session *session, struct tls_ctx_t *ctx); +void session_tls_set_server_ctx(struct session *session, struct tls_ctx *ctx); /** Get pointer to client-side tls-related data. */ -struct tls_client_ctx_t *session_tls_get_client_ctx(const struct session *session); +struct tls_client_ctx *session_tls_get_client_ctx(const struct session *session); /** Set pointer to client-side tls-related data. */ -void session_tls_set_client_ctx(struct session *session, struct tls_client_ctx_t *ctx); -/** Get pointer to that part of tls-related data which has common structure for +void session_tls_set_client_ctx(struct session *session, struct tls_client_ctx *ctx); +/** Get pointer to that part of tls-related data which has common structure for * server and client. */ struct tls_common_ctx *session_tls_get_common_ctx(const struct session *session); +#if ENABLE_DOH2 +/** Get pointer to server-side http-related data. */ +struct http_ctx *session_http_get_server_ctx(const struct session *session); +/** Set pointer to server-side http-related data. */ +void session_http_set_server_ctx(struct session *session, struct http_ctx *ctx); +#endif + /** Get pointer to underlying libuv handle for IO operations. */ uv_handle_t *session_get_handle(struct session *session); struct session *session_get(uv_handle_t *h); @@ -131,9 +139,9 @@ knot_pkt_t *session_produce_packet(struct session *session, knot_mm_t *mm); int session_discard_packet(struct session *session, const knot_pkt_t *pkt); -void session_kill_ioreq(struct session *s, struct qr_task *task); +void session_kill_ioreq(struct session *session, struct qr_task *task); /** Update timestamp */ -void session_touch(struct session *s); +void session_touch(struct session *session); /** Returns either creation time or time of last IO activity if any occurs. */ /* Used for TCP timeout calculation. */ -uint64_t session_last_activity(struct session *s); +uint64_t session_last_activity(struct session *session); diff -Nru knot-resolver-5.1.1/daemon/tls.c knot-resolver-5.2.1/daemon/tls.c --- knot-resolver-5.1.1/daemon/tls.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/tls.c 2020-12-09 09:44:29.000000000 +0000 @@ -155,6 +155,8 @@ if (ret < 0 && ret != UV_EAGAIN) { /* uv_try_write() has returned error code other then UV_EAGAIN. * Return. */ + kr_log_verbose("[%s] uv_try_write error: %s\n", + t->client_side ? "tls_client" : "tls", uv_strerror(ret)); ret = -1; errno = EIO; return ret; @@ -212,6 +214,8 @@ t->write_queue_size += 1; } else { free(p); + kr_log_verbose("[%s] uv_write error: %s\n", + t->client_side ? "tls_client" : "tls", uv_strerror(ret)); errno = EIO; ret = -1; } @@ -253,6 +257,8 @@ kr_log_verbose("[%s] gnutls_handshake failed: %s (%d)\n", logstring, gnutls_strerror_name(err), err); + /* Notify the peer about handshake failure via an alert. */ + gnutls_alert_send_appropriate(ctx->tls_session, err); if (handshake_cb) { handshake_cb(session, -1); } @@ -270,7 +276,7 @@ } -struct tls_ctx_t *tls_new(struct worker_ctx *worker) +struct tls_ctx *tls_new(struct worker_ctx *worker) { assert(worker != NULL); assert(worker->engine != NULL); @@ -309,7 +315,7 @@ } } - struct tls_ctx_t *tls = calloc(1, sizeof(struct tls_ctx_t)); + struct tls_ctx *tls = calloc(1, sizeof(struct tls_ctx)); if (tls == NULL) { kr_log_error("[tls] failed to allocate TLS context\n"); return NULL; @@ -367,7 +373,7 @@ } } -void tls_free(struct tls_ctx_t *tls) +void tls_free(struct tls_ctx *tls) { if (!tls) { return; @@ -403,8 +409,8 @@ ssize_t count = 0; if ((count = gnutls_record_send(tls_session, &pkt_size, sizeof(pkt_size)) < 0) || (count = gnutls_record_send(tls_session, pkt->wire, pkt->size) < 0)) { - kr_log_error("[%s] gnutls_record_send failed: %s (%zd)\n", - logstring, gnutls_strerror_name(count), count); + kr_log_verbose("[%s] gnutls_record_send failed: %s (%zd)\n", + logstring, gnutls_strerror_name(count), count); return kr_error(EIO); } @@ -415,8 +421,8 @@ if (!gnutls_error_is_fatal(ret)) { return kr_error(EAGAIN); } else { - kr_log_error("[%s] gnutls_record_uncork failed: %s (%d)\n", - logstring, gnutls_strerror_name(ret), ret); + kr_log_verbose("[%s] gnutls_record_uncork failed: %s (%d)\n", + logstring, gnutls_strerror_name(ret), ret); return kr_error(EIO); } } @@ -1013,7 +1019,7 @@ */ static int client_verify_certificate(gnutls_session_t tls_session) { - struct tls_client_ctx_t *ctx = gnutls_session_get_ptr(tls_session); + struct tls_client_ctx *ctx = gnutls_session_get_ptr(tls_session); assert(ctx->params != NULL); if (ctx->params->insecure) { @@ -1041,10 +1047,10 @@ return client_verify_certchain(ctx->c.tls_session, ctx->params->hostname); } -struct tls_client_ctx_t *tls_client_ctx_new(tls_client_param_t *entry, +struct tls_client_ctx *tls_client_ctx_new(tls_client_param_t *entry, struct worker_ctx *worker) { - struct tls_client_ctx_t *ctx = calloc(1, sizeof (struct tls_client_ctx_t)); + struct tls_client_ctx *ctx = calloc(1, sizeof (struct tls_client_ctx)); if (!ctx) { return NULL; } @@ -1093,7 +1099,7 @@ return ctx; } -void tls_client_ctx_free(struct tls_client_ctx_t *ctx) +void tls_client_ctx_free(struct tls_client_ctx *ctx) { if (ctx == NULL) { return; @@ -1124,7 +1130,7 @@ return avail; } -int tls_client_connect_start(struct tls_client_ctx_t *client_ctx, +int tls_client_connect_start(struct tls_client_ctx *client_ctx, struct session *session, tls_handshake_cb handshake_cb) { @@ -1174,7 +1180,7 @@ return kr_ok(); } -int tls_client_ctx_set_session(struct tls_client_ctx_t *ctx, struct session *session) +int tls_client_ctx_set_session(struct tls_client_ctx *ctx, struct session *session) { if (!ctx) { return kr_error(EINVAL); diff -Nru knot-resolver-5.1.1/daemon/tls.h knot-resolver-5.2.1/daemon/tls.h --- knot-resolver-5.1.1/daemon/tls.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/tls.h 2020-12-09 09:44:29.000000000 +0000 @@ -34,8 +34,8 @@ /** Transport session (opaque). */ struct session; -struct tls_ctx_t; -struct tls_client_ctx_t; +struct tls_ctx; +struct tls_client_ctx; struct tls_credentials { int count; char *tls_cert; @@ -125,9 +125,9 @@ size_t write_queue_size; }; -struct tls_ctx_t { +struct tls_ctx { /* - * Since pointer to tls_ctx_t needs to be casted + * Since pointer to tls_ctx needs to be casted * to tls_ctx_common in some functions, * this field must be always at first position */ @@ -135,9 +135,9 @@ struct tls_credentials *credentials; }; -struct tls_client_ctx_t { +struct tls_client_ctx { /* - * Since pointer to tls_client_ctx_t needs to be casted + * Since pointer to tls_client_ctx needs to be casted * to tls_ctx_common in some functions, * this field must be always at first position */ @@ -146,13 +146,13 @@ }; /*! Create an empty TLS context in query context */ -struct tls_ctx_t* tls_new(struct worker_ctx *worker); +struct tls_ctx* tls_new(struct worker_ctx *worker); /*! Close a TLS context (call gnutls_bye()) */ void tls_close(struct tls_common_ctx *ctx); /*! Release a TLS context */ -void tls_free(struct tls_ctx_t* tls); +void tls_free(struct tls_ctx* tls); /*! Push new data to TLS context for sending */ int tls_write(uv_write_t *req, uv_handle_t* handle, knot_pkt_t * pkt, uv_write_cb cb); @@ -189,17 +189,17 @@ /*! Allocate new client TLS context */ -struct tls_client_ctx_t *tls_client_ctx_new(tls_client_param_t *entry, +struct tls_client_ctx *tls_client_ctx_new(tls_client_param_t *entry, struct worker_ctx *worker); /*! Free client TLS context */ -void tls_client_ctx_free(struct tls_client_ctx_t *ctx); +void tls_client_ctx_free(struct tls_client_ctx *ctx); -int tls_client_connect_start(struct tls_client_ctx_t *client_ctx, +int tls_client_connect_start(struct tls_client_ctx *client_ctx, struct session *session, tls_handshake_cb handshake_cb); -int tls_client_ctx_set_session(struct tls_client_ctx_t *ctx, struct session *session); +int tls_client_ctx_set_session(struct tls_client_ctx *ctx, struct session *session); /* Session tickets, server side. Implementation in ./tls_session_ticket-srv.c */ diff -Nru knot-resolver-5.1.1/daemon/tls_session_ticket-srv.c knot-resolver-5.2.1/daemon/tls_session_ticket-srv.c --- knot-resolver-5.1.1/daemon/tls_session_ticket-srv.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/tls_session_ticket-srv.c 2020-12-09 09:44:29.000000000 +0000 @@ -26,11 +26,13 @@ /** Compile-time support for setting the secret. */ /* This is not secure with TLS <= 1.2 but TLS 1.3 and secure configuration - * is not available in GnuTLS yet. See https://gitlab.com/gnutls/gnutls/issues/477 -#ifndef TLS_SESSION_RESUMPTION_SYNC - #define TLS_SESSION_RESUMPTION_SYNC (GNUTLS_VERSION_NUMBER >= 0x030603) + * is not available in GnuTLS yet. See https://gitlab.com/gnutls/gnutls/issues/477 */ +#define TLS_SESSION_RESUMPTION_SYNC (GNUTLS_VERSION_NUMBER >= 0x030603) +#if TLS_SESSION_RESUMPTION_SYNC + #define TST_HASH GNUTLS_DIG_SHA3_512 +#else + #define TST_HASH abort() #endif -*/ #if GNUTLS_VERSION_NUMBER < 0x030400 /* It's of little use anyway. We may get the secret through lua, @@ -38,12 +40,6 @@ #define gnutls_memset memset #endif -#ifdef GNUTLS_DIG_SHA3_512 - #define TST_HASH GNUTLS_DIG_SHA3_512 -#else - #define TST_HASH abort() -#endif - /** Fields are internal to tst_key_* functions. */ typedef struct tls_session_ticket_ctx { uv_timer_t timer; /**< timer for rotation of the key */ diff -Nru knot-resolver-5.1.1/daemon/worker.c knot-resolver-5.2.1/daemon/worker.c --- knot-resolver-5.1.1/daemon/worker.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/worker.c 2020-12-09 09:44:29.000000000 +0000 @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2017 CZ.NIC, z.s.p.o. +/* Copyright (C) 2014-2020 CZ.NIC, z.s.p.o. * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -21,11 +22,16 @@ #include #include +#if ENABLE_XDP + #include +#endif + #include "daemon/bindings/api.h" #include "daemon/engine.h" #include "daemon/io.h" #include "daemon/session.h" #include "daemon/tls.h" +#include "daemon/http.h" #include "daemon/udp_queue.h" #include "daemon/zimport.h" #include "lib/layer.h" @@ -54,15 +60,19 @@ { struct kr_request req; + struct worker_ctx *worker; + struct qr_task *task; struct { - /** Requestor's address; separate because of UDP session "sharing". */ - union inaddr addr; /** NULL if the request didn't come over network. */ struct session *session; + /** Requestor's address; separate because of UDP session "sharing". */ + union inaddr addr; + /** Local address. For AF_XDP we couldn't use session's, + * as the address might be different every time. */ + union inaddr dst_addr; + /** MAC addresses - ours [0] and router's [1], in case of AF_XDP socket. */ + uint8_t eth_addrs[2][6]; } source; - - struct worker_ctx *worker; - struct qr_task *task; }; /** Query resolution task. */ @@ -118,6 +128,9 @@ static struct session* worker_find_tcp_waiting(struct worker_ctx *worker, const struct sockaddr *addr); static void on_tcp_connect_timeout(uv_timer_t *timer); +static void on_retransmit(uv_timer_t *req); +static void subreq_finalize(struct qr_task *task, const struct sockaddr *packet_source, knot_pkt_t *pkt); + struct worker_ctx the_worker_value; /**< Static allocation is suitable for the singleton. */ struct worker_ctx *the_worker = NULL; @@ -125,7 +138,8 @@ /*! @internal Create a UDP/TCP handle for an outgoing AF_INET* connection. * socktype is SOCK_* */ static uv_handle_t *ioreq_spawn(struct worker_ctx *worker, - int socktype, sa_family_t family, bool has_tls) + int socktype, sa_family_t family, bool has_tls, + bool has_http) { bool precond = (socktype == SOCK_DGRAM || socktype == SOCK_STREAM) && (family == AF_INET || family == AF_INET6); @@ -141,7 +155,7 @@ if (!handle) { return NULL; } - int ret = io_create(worker->loop, handle, socktype, family, has_tls); + int ret = io_create(worker->loop, handle, socktype, family, has_tls, has_http); if (ret) { if (ret == UV_EMFILE) { worker->too_many_open = true; @@ -170,8 +184,7 @@ } if (ret != 0) { - io_deinit(handle); - free(handle); + io_free(handle); return NULL; } @@ -252,14 +265,63 @@ knot_pkt_qtype(pkt), knot_pkt_qtype(pkt)); } +#if ENABLE_XDP +static uint8_t *alloc_wire_cb(struct kr_request *req, uint16_t *maxlen) +{ + assert(maxlen); + struct request_ctx *ctx = (struct request_ctx *)req; + /* We know it's an AF_XDP socket; otherwise this CB isn't assigned. */ + uv_handle_t *handle = session_get_handle(ctx->source.session); + assert(handle->type == UV_POLL); + xdp_handle_data_t *xhd = handle->data; + knot_xdp_msg_t out; + int ret = knot_xdp_send_alloc(xhd->socket, ctx->source.addr.ip.sa_family == AF_INET6, + &out, NULL); + if (ret != KNOT_EOK) { + assert(ret == KNOT_ENOMEM); + *maxlen = 0; + return NULL; + } + *maxlen = MIN(*maxlen, out.payload.iov_len); + /* It's most convenient to fill the MAC addresses at this point. */ + memcpy(out.eth_from, &ctx->source.eth_addrs[0], 6); + memcpy(out.eth_to, &ctx->source.eth_addrs[1], 6); + return out.payload.iov_base; +} +static void free_wire(const struct request_ctx *ctx) +{ + assert(ctx->req.alloc_wire_cb == alloc_wire_cb); + knot_pkt_t *ans = ctx->req.answer; + if (unlikely(ans == NULL)) /* dropped */ + return; + if (likely(ans->wire == NULL)) /* sent most likely */ + return; + /* We know it's an AF_XDP socket; otherwise alloc_wire_cb isn't assigned. */ + uv_handle_t *handle = session_get_handle(ctx->source.session); + assert(handle->type == UV_POLL); + xdp_handle_data_t *xhd = handle->data; + /* Freeing is done by sending an empty packet (the API won't really send it). */ + knot_xdp_msg_t out; + out.payload.iov_base = ans->wire; + out.payload.iov_len = 0; + uint32_t sent; + int ret = knot_xdp_send(xhd->socket, &out, 1, &sent); + assert(ret == KNOT_EOK && sent == 0); (void)ret; + kr_log_verbose("[xdp] freed unsent buffer, ret = %d\n", ret); +} +#endif + /** Create and initialize a request_ctx (on a fresh mempool). * - * handle and addr point to the source of the request, and they are NULL + * session and addr point to the source of the request, and they are NULL * in case the request didn't come from network. */ static struct request_ctx *request_create(struct worker_ctx *worker, struct session *session, - const struct sockaddr *peer, + const struct sockaddr *addr, + const struct sockaddr *dst_addr, + const uint8_t *eth_from, + const uint8_t *eth_to, uint32_t uid) { knot_mm_t pool = { @@ -282,19 +344,43 @@ assert(session_flags(session)->outgoing == false); } ctx->source.session = session; + assert(!!eth_to == !!eth_from); + const bool is_xdp = eth_to != NULL; + if (is_xdp) { + #if ENABLE_XDP + assert(session); + memcpy(&ctx->source.eth_addrs[0], eth_to, sizeof(ctx->source.eth_addrs[0])); + memcpy(&ctx->source.eth_addrs[1], eth_from, sizeof(ctx->source.eth_addrs[1])); + ctx->req.alloc_wire_cb = alloc_wire_cb; + #else + assert(!EINVAL); + return NULL; + #endif + } struct kr_request *req = &ctx->req; req->pool = pool; req->vars_ref = LUA_NOREF; req->uid = uid; + req->qsource.flags.xdp = is_xdp; if (session) { - /* We assume the session will be alive during the whole life of the request. */ - req->qsource.dst_addr = session_get_sockname(session); req->qsource.flags.tcp = session_get_handle(session)->type == UV_TCP; req->qsource.flags.tls = session_flags(session)->has_tls; + req->qsource.flags.http = session_flags(session)->has_http; + req->qsource.stream_id = -1; +#if ENABLE_DOH2 + if (req->qsource.flags.http) { + struct http_ctx *http_ctx = session_http_get_server_ctx(session); + req->qsource.stream_id = queue_head(http_ctx->streams); + } +#endif /* We need to store a copy of peer address. */ - memcpy(&ctx->source.addr.ip, peer, kr_sockaddr_len(peer)); + memcpy(&ctx->source.addr.ip, addr, kr_sockaddr_len(addr)); req->qsource.addr = &ctx->source.addr.ip; + if (!dst_addr) /* We wouldn't have to copy in this case, but for consistency. */ + dst_addr = session_get_sockname(session); + memcpy(&ctx->source.dst_addr.ip, dst_addr, kr_sockaddr_len(dst_addr)); + req->qsource.dst_addr = &ctx->source.dst_addr.ip; } worker->stats.rconcurrent += 1; @@ -306,27 +392,13 @@ static int request_start(struct request_ctx *ctx, knot_pkt_t *query) { assert(query && ctx); - size_t answer_max = KNOT_WIRE_MIN_PKTSIZE; - struct kr_request *req = &ctx->req; - /* source.session can be empty if request was generated by kresd itself */ - struct session *s = ctx->source.session; - if (!s || session_get_handle(s)->type == UV_TCP) { - answer_max = KNOT_WIRE_MAX_PKTSIZE; - } else if (knot_pkt_has_edns(query)) { /* EDNS */ - answer_max = MAX(knot_edns_get_payload(query->opt_rr), - KNOT_WIRE_MIN_PKTSIZE); - } + struct kr_request *req = &ctx->req; req->qsource.size = query->size; if (knot_pkt_has_tsig(query)) { req->qsource.size += query->tsig_wire.len; } - knot_pkt_t *answer = knot_pkt_new(NULL, answer_max, &req->pool); - if (!answer) { /* Failed to allocate answer */ - return kr_error(ENOMEM); - } - knot_pkt_t *pkt = knot_pkt_new(NULL, req->qsource.size, &req->pool); if (!pkt) { return kr_error(ENOMEM); @@ -341,7 +413,7 @@ /* Start resolution */ struct worker_ctx *worker = ctx->worker; struct engine *engine = worker->engine; - kr_resolve_begin(req, &engine->resolver, answer); + kr_resolve_begin(req, &engine->resolver); worker->stats.queries += 1; /* Throttle outbound queries only when high pressure */ if (worker->stats.concurrent < QUERY_RATE_THRESHOLD) { @@ -367,6 +439,14 @@ lua_pop(L, 1); ctx->req.vars_ref = LUA_NOREF; } + /* Make sure to free XDP buffer in case it wasn't sent. */ + if (ctx->req.alloc_wire_cb) { + #if ENABLE_XDP + free_wire(ctx); + #else + assert(!EINVAL); + #endif + } /* Return mempool to ring or free it if it's full */ pool_release(worker, ctx->req.pool.ctx); /* @note The 'task' is invalidated from now on. */ @@ -377,9 +457,9 @@ { /* Choose (initial) pktbuf size. As it is now, pktbuf can be used * for UDP answers from upstream *and* from cache - * and for sending non-UDP queries upstream (?) */ + * and for sending queries upstream */ uint16_t pktbuf_max = KR_EDNS_PAYLOAD; - const knot_rrset_t *opt_our = ctx->worker->engine->resolver.opt_rr; + const knot_rrset_t *opt_our = ctx->worker->engine->resolver.upstream_opt_rr; if (opt_our) { pktbuf_max = MAX(pktbuf_max, knot_edns_get_payload(opt_our)); } @@ -477,7 +557,7 @@ } /* This is called when we send subrequest / answer */ -int qr_task_on_send(struct qr_task *task, uv_handle_t *handle, int status) +int qr_task_on_send(struct qr_task *task, const uv_handle_t *handle, int status) { if (task->finished) { @@ -485,27 +565,52 @@ qr_task_complete(task); } - if (!handle || handle->type != UV_TCP) { + if (!handle) return status; - } struct session* s = handle->data; assert(s); - if (status != 0) { - session_tasklist_del(s, task); - } - if (session_flags(s)->outgoing || session_flags(s)->closing) { - return status; + if (handle->type == UV_UDP && session_flags(s)->outgoing) { + /* Start the timeout timer for UDP here, since this is the closest + * to the wire we can get. */ + struct kr_request *req = &task->ctx->req; + /* Check current query NSLIST */ + struct kr_query *qry = array_tail(req->rplan.pending); + assert(qry != NULL); + /* Retransmit at default interval, or more frequently if the mean + * RTT of the server is better. If the server is glued, use default rate. */ + size_t timeout = qry->ns.score; + if (timeout > KR_NS_GLUED) { + /* We don't have information about variance in RTT, expect +10ms */ + timeout = MIN(qry->ns.score + 10, KR_CONN_RETRY); + } else { + timeout = KR_CONN_RETRY; + } + + int ret = session_timer_start(s, on_retransmit, timeout, 0); + /* Start next step with timeout, fatal if can't start a timer. */ + if (ret != 0) { + subreq_finalize(task, &qry->ns.addr->ip, task->pktbuf); + qr_task_finalize(task, KR_STATE_FAIL); + } } - struct worker_ctx *worker = task->ctx->worker; - if (session_flags(s)->throttled && - session_tasklist_get_len(s) < worker->tcp_pipeline_max/2) { - /* Start reading again if the session is throttled and - * the number of outgoing requests is below watermark. */ - session_start_read(s); - session_flags(s)->throttled = false; + if (handle->type == UV_TCP) { + if (status != 0) + session_tasklist_del(s, task); + + if (session_flags(s)->outgoing || session_flags(s)->closing) + return status; + + struct worker_ctx *worker = task->ctx->worker; + if (session_flags(s)->throttled && + session_tasklist_get_len(s) < worker->tcp_pipeline_max/2) { + /* Start reading again if the session is throttled and + * the number of outgoing requests is below watermark. */ + session_start_read(s); + session_flags(s)->throttled = false; + } } return status; @@ -578,7 +683,15 @@ struct worker_ctx *worker = ctx->worker; /* Send using given protocol */ assert(!session_flags(session)->closing); - if (session_flags(session)->has_tls) { + if (session_flags(session)->has_http) { +#if ENABLE_DOH2 + uv_write_t *write_req = (uv_write_t *)ioreq; + write_req->data = task; + ret = http_write(write_req, handle, pkt, ctx->req.qsource.stream_id, &on_write); +#else + ret = kr_error(ENOPROTOOPT); +#endif + } else if (session_flags(session)->has_tls) { uv_write_t *write_req = (uv_write_t *)ioreq; write_req->data = task; ret = tls_write(write_req, handle, pkt, &on_write); @@ -633,9 +746,18 @@ worker->rconcurrent_highwatermark = worker->stats.rconcurrent; ret = kr_error(UV_EMFILE); } + + if (session_flags(session)->has_http) + worker->stats.err_http += 1; + else if (session_flags(session)->has_tls) + worker->stats.err_tls += 1; + else if (handle->type == UV_UDP) + worker->stats.err_udp += 1; + else + worker->stats.err_tcp += 1; } - /* Update statistics */ + /* Update outgoing query statistics */ if (session_flags(session)->outgoing && addr) { if (session_flags(session)->has_tls) worker->stats.tls += 1; @@ -664,11 +786,8 @@ static int session_tls_hs_cb(struct session *session, int status) { assert(session_flags(session)->outgoing); - uv_handle_t *handle = session_get_handle(session); - uv_loop_t *loop = handle->loop; - struct worker_ctx *worker = loop->data; struct sockaddr *peer = session_get_peer(session); - int deletion_res = worker_del_tcp_waiting(worker, peer); + int deletion_res = worker_del_tcp_waiting(the_worker, peer); int ret = kr_ok(); if (status) { @@ -677,7 +796,7 @@ struct kr_qflags *options = &task->ctx->req.options; unsigned score = options->FORWARD || options->STUB ? KR_NS_FWD_DEAD : KR_NS_DEAD; kr_nsrep_update_rtt(NULL, peer, score, - worker->engine->resolver.cache_rtt, + the_worker->engine->resolver.cache_rtt, KR_NS_UPDATE_NORESET); } #ifndef NDEBUG @@ -689,14 +808,14 @@ assert(deletion_res != 0); const char *key = tcpsess_key(peer); assert(key); - assert(map_contains(&worker->tcp_connected, key) != 0); + assert(map_contains(&the_worker->tcp_connected, key) != 0); } #endif return ret; } /* handshake was completed successfully */ - struct tls_client_ctx_t *tls_client_ctx = session_tls_get_client_ctx(session); + struct tls_client_ctx *tls_client_ctx = session_tls_get_client_ctx(session); tls_client_param_t *tls_params = tls_client_ctx->params; gnutls_session_t tls_session = tls_client_ctx->c.tls_session; if (gnutls_session_is_resumed(tls_session) != 0) { @@ -717,7 +836,7 @@ } } - struct session *s = worker_find_tcp_connected(worker, peer); + struct session *s = worker_find_tcp_connected(the_worker, peer); ret = kr_ok(); if (deletion_res == kr_ok()) { /* peer was in the waiting list, add to the connected list. */ @@ -726,7 +845,7 @@ * peer already is in the connected list. */ ret = kr_error(EINVAL); } else { - ret = worker_add_tcp_connected(worker, peer, session); + ret = worker_add_tcp_connected(the_worker, peer, session); } } else { /* peer wasn't in the waiting list. @@ -758,9 +877,9 @@ /* Something went wrong. * Either addition to the list of connected sessions * or write to upstream failed. */ - worker_del_tcp_connected(worker, peer); + worker_del_tcp_connected(the_worker, peer); session_waitinglist_finalize(session, KR_STATE_FAIL); - assert(session_tasklist_is_empty(session)); + session_tasklist_finalize(session, KR_STATE_FAIL); session_close(session); } else { session_timer_stop(session); @@ -891,7 +1010,7 @@ int ret = kr_ok(); if (session_flags(session)->has_tls) { - struct tls_client_ctx_t *tls_ctx = session_tls_get_client_ctx(session); + struct tls_client_ctx *tls_ctx = session_tls_get_client_ctx(session); ret = tls_client_connect_start(tls_ctx, session, session_tls_hs_cb); if (ret == kr_error(EAGAIN)) { session_timer_stop(session); @@ -1009,7 +1128,7 @@ if (kr_resolve_checkout(&ctx->req, NULL, (struct sockaddr *)choice, SOCK_DGRAM, task->pktbuf) != 0) { return ret; } - ret = ioreq_spawn(ctx->worker, SOCK_DGRAM, choice->sin6_family, false); + ret = ioreq_spawn(ctx->worker, SOCK_DGRAM, choice->sin6_family, false, false); if (!ret) { return ret; } @@ -1026,7 +1145,7 @@ task->pending[task->pending_count] = session; task->pending_count += 1; task->addrlist_turn = (task->addrlist_turn + 1) % - task->addrlist_count; /* Round robin */ + task->addrlist_count; /* Round robin */ session_start_read(session); /* Start reading answer */ } } @@ -1125,11 +1244,55 @@ return true; } +#if ENABLE_XDP +static void xdp_tx_waker(uv_idle_t *handle) +{ + int ret = knot_xdp_send_finish(handle->data); + if (ret != KNOT_EAGAIN && ret != KNOT_EOK) + kr_log_error("[xdp] check: ret = %d, %s\n", ret, knot_strerror(ret)); + /* Apparently some drivers need many explicit wake-up calls + * even if we push no additional packets (in case they accumulated a lot) */ + if (ret != KNOT_EAGAIN) + uv_idle_stop(handle); + knot_xdp_send_prepare(handle->data); + /* LATER(opt.): it _might_ be better for performance to do these two steps + * at different points in time */ +} +#endif +/** Send an answer packet over XDP. */ +static int xdp_push(struct qr_task *task, const uv_handle_t *src_handle) +{ +#if ENABLE_XDP + struct request_ctx *ctx = task->ctx; + knot_xdp_msg_t msg; + const struct sockaddr *ip_from = &ctx->source.dst_addr.ip; + const struct sockaddr *ip_to = &ctx->source.addr.ip; + memcpy(&msg.ip_from, ip_from, kr_sockaddr_len(ip_from)); + memcpy(&msg.ip_to, ip_to, kr_sockaddr_len(ip_to)); + msg.payload.iov_base = ctx->req.answer->wire; + msg.payload.iov_len = ctx->req.answer->size; + + xdp_handle_data_t *xhd = src_handle->data; + assert(xhd && xhd->socket && xhd->session == ctx->source.session); + uint32_t sent; + int ret = knot_xdp_send(xhd->socket, &msg, 1, &sent); + ctx->req.answer->wire = NULL; /* it's been freed */ + + uv_idle_start(&xhd->tx_waker, xdp_tx_waker); + kr_log_verbose("[xdp] pushed a packet, ret = %d\n", ret); + + return qr_task_on_send(task, src_handle, ret); +#else + assert(!EINVAL); + return kr_error(EINVAL); +#endif +} + static int qr_task_finalize(struct qr_task *task, int state) { assert(task && task->leading == false); if (task->finished) { - return 0; + return kr_ok(); } struct request_ctx *ctx = task->ctx; struct session *source_session = ctx->source.session; @@ -1138,21 +1301,32 @@ task->finished = true; if (source_session == NULL) { (void) qr_task_on_send(task, NULL, kr_error(EIO)); - return state == KR_STATE_DONE ? 0 : kr_error(EIO); + return state == KR_STATE_DONE ? kr_ok() : kr_error(EIO); } + if (unlikely(ctx->req.answer == NULL)) { /* meant to be dropped */ + (void) qr_task_on_send(task, NULL, kr_ok()); + return kr_ok(); + } + + if (session_flags(source_session)->closing || + ctx->source.addr.ip.sa_family == AF_UNSPEC) + return kr_error(EINVAL); + /* Reference task as the callback handler can close it */ qr_task_ref(task); /* Send back answer */ - assert(!session_flags(source_session)->closing); - assert(ctx->source.addr.ip.sa_family != AF_UNSPEC); - int ret; const uv_handle_t *src_handle = session_get_handle(source_session); - if (src_handle->type != UV_UDP && src_handle->type != UV_TCP) { + if (src_handle->type != UV_UDP && src_handle->type != UV_TCP + && src_handle->type != UV_POLL) { assert(false); ret = kr_error(EINVAL); + + } else if (src_handle->type == UV_POLL) { + ret = xdp_push(task, src_handle); + } else if (src_handle->type == UV_UDP && ENABLE_SENDMMSG) { int fd; ret = uv_fileno(src_handle, &fd); @@ -1182,15 +1356,14 @@ qr_task_unref(task); - return state == KR_STATE_DONE ? 0 : kr_error(EIO); + if (ret != kr_ok() || state != KR_STATE_DONE) + return kr_error(EIO); + return kr_ok(); } static int udp_task_step(struct qr_task *task, const struct sockaddr *packet_source, knot_pkt_t *packet) { - struct request_ctx *ctx = task->ctx; - struct kr_request *req = &ctx->req; - /* If there is already outgoing query, enqueue to it. */ if (subreq_enqueue(task)) { return kr_ok(); /* Will be notified when outgoing query finishes. */ @@ -1201,30 +1374,12 @@ subreq_finalize(task, packet_source, packet); return qr_task_finalize(task, KR_STATE_FAIL); } - /* Check current query NSLIST */ - struct kr_query *qry = array_tail(req->rplan.pending); - assert(qry != NULL); - /* Retransmit at default interval, or more frequently if the mean - * RTT of the server is better. If the server is glued, use default rate. */ - size_t timeout = qry->ns.score; - if (timeout > KR_NS_GLUED) { - /* We don't have information about variance in RTT, expect +10ms */ - timeout = MIN(qry->ns.score + 10, KR_CONN_RETRY); - } else { - timeout = KR_CONN_RETRY; - } + /* Announce and start subrequest. * @note Only UDP can lead I/O as it doesn't touch 'task->pktbuf' for reassembly. */ subreq_lead(task); - struct session *session = handle->data; - assert(session_get_handle(session) == handle && (handle->type == UV_UDP)); - int ret = session_timer_start(session, on_retransmit, timeout, 0); - /* Start next step with timeout, fatal if can't start a timer. */ - if (ret != 0) { - subreq_finalize(task, packet_source, packet); - return qr_task_finalize(task, KR_STATE_FAIL); - } + return kr_ok(); } @@ -1293,7 +1448,7 @@ struct worker_ctx *worker = ctx->worker; /* Check if there must be TLS */ - struct tls_client_ctx_t *tls_ctx = NULL; + struct tls_client_ctx *tls_ctx = NULL; struct network *net = &worker->engine->net; tls_client_param_t *entry = tls_client_param_get(net->tls_client_params, addr); if (entry) { @@ -1310,8 +1465,9 @@ tls_client_ctx_free(tls_ctx); return kr_error(EINVAL); } + bool has_http = false; bool has_tls = (tls_ctx != NULL); - uv_handle_t *client = ioreq_spawn(worker, SOCK_STREAM, addr->sa_family, has_tls); + uv_handle_t *client = ioreq_spawn(worker, SOCK_STREAM, addr->sa_family, has_tls, has_http); if (!client) { tls_client_ctx_free(tls_ctx); free(conn); @@ -1562,32 +1718,38 @@ return ret; } -int worker_submit(struct session *session, const struct sockaddr *peer, knot_pkt_t *query) +int worker_submit(struct session *session, + const struct sockaddr *peer, const struct sockaddr *dst_addr, + const uint8_t *eth_from, const uint8_t *eth_to, knot_pkt_t *pkt) { - if (!session) { - assert(false); + if (!session || !pkt) return kr_error(EINVAL); - } uv_handle_t *handle = session_get_handle(session); - bool OK = handle && handle->loop->data; - if (!OK) { - assert(false); + if (!handle || !handle->loop->data) return kr_error(EINVAL); - } - struct worker_ctx *worker = handle->loop->data; + int ret = parse_packet(pkt); - /* Parse packet */ - int ret = parse_packet(query); - - const bool is_query = (knot_wire_get_qr(query->wire) == 0); + const bool is_query = (knot_wire_get_qr(pkt->wire) == 0); const bool is_outgoing = session_flags(session)->outgoing; + + struct http_ctx *http_ctx = NULL; +#if ENABLE_DOH2 + http_ctx = session_http_get_server_ctx(session); +#endif + + if (!is_outgoing && http_ctx && queue_len(http_ctx->streams) <= 0) + return kr_error(ENOENT); + /* Ignore badly formed queries. */ - if (!query || - (ret != kr_ok() && ret != kr_error(EMSGSIZE)) || + if ((ret != kr_ok() && ret != kr_error(EMSGSIZE)) || (is_query == is_outgoing)) { - if (query && !is_outgoing) worker->stats.dropped += 1; + if (!is_outgoing) { + the_worker->stats.dropped += 1; + if (http_ctx) + queue_pop(http_ctx->streams); + } return kr_error(EILSEQ); } @@ -1596,13 +1758,15 @@ struct qr_task *task = NULL; const struct sockaddr *addr = NULL; if (!is_outgoing) { /* request from a client */ - struct request_ctx *ctx = request_create(worker, session, peer, - knot_wire_get_id(query->wire)); - if (!ctx) { + struct request_ctx *ctx = + request_create(the_worker, session, peer, dst_addr, + eth_from, eth_to, knot_wire_get_id(pkt->wire)); + if (http_ctx) + queue_pop(http_ctx->streams); + if (!ctx) return kr_error(ENOMEM); - } - ret = request_start(ctx, query); + ret = request_start(ctx, pkt); if (ret != 0) { request_free(ctx); return kr_error(ENOMEM); @@ -1617,8 +1781,8 @@ if (handle->type == UV_TCP && qr_task_register(task, session)) { return kr_error(ENOMEM); } - } else if (query) { /* response from upstream */ - const uint16_t id = knot_wire_get_id(query->wire); + } else { /* response from upstream */ + const uint16_t id = knot_wire_get_id(pkt->wire); task = session_tasklist_del_msgid(session, id); if (task == NULL) { VERBOSE_MSG(NULL, "=> ignoring packet with mismatching ID %d\n", @@ -1634,7 +1798,7 @@ * Task was created (found). */ session_touch(session); /* Consume input and produce next message */ - return qr_task_step(task, addr, query); + return qr_task_step(task, addr, pkt); } static int map_add_tcp_session(map_t *map, const struct sockaddr* addr, @@ -1727,21 +1891,19 @@ session_timer_stop(session); - uv_handle_t *handle = session_get_handle(session); - struct worker_ctx *worker = handle->loop->data; struct sockaddr *peer = session_get_peer(session); - worker_del_tcp_waiting(worker, peer); - worker_del_tcp_connected(worker, peer); + worker_del_tcp_waiting(the_worker, peer); + worker_del_tcp_connected(the_worker, peer); session_flags(session)->connected = false; - struct tls_client_ctx_t *tls_client_ctx = session_tls_get_client_ctx(session); + struct tls_client_ctx *tls_client_ctx = session_tls_get_client_ctx(session); if (tls_client_ctx) { /* Avoid gnutls_bye() call */ tls_set_hs_state(&tls_client_ctx->c, TLS_HS_NOT_STARTED); } - struct tls_ctx_t *tls_ctx = session_tls_get_server_ctx(session); + struct tls_ctx *tls_ctx = session_tls_get_server_ctx(session); if (tls_ctx) { /* Avoid gnutls_bye() call */ tls_set_hs_state(&tls_ctx->c, TLS_HS_NOT_STARTED); @@ -1804,7 +1966,7 @@ /* Add OPT RR, including wire format so modules can see both representations. * knot_pkt_put() copies the outside; we need to duplicate the inside manually. */ - knot_rrset_t *opt = knot_rrset_copy(the_worker->engine->resolver.opt_rr, NULL); + knot_rrset_t *opt = knot_rrset_copy(the_worker->engine->resolver.downstream_opt_rr, NULL); if (!opt) { knot_pkt_free(pkt); return NULL; @@ -1838,7 +2000,8 @@ } - struct request_ctx *ctx = request_create(worker, NULL, NULL, worker->next_request_uid); + struct request_ctx *ctx = request_create(worker, NULL, NULL, NULL, NULL, NULL, + worker->next_request_uid); if (!ctx) { return NULL; } @@ -2022,7 +2185,7 @@ the_worker = NULL; } -int worker_init(struct engine *engine, int worker_id, int worker_count) +int worker_init(struct engine *engine, int worker_count) { assert(engine && engine->L); assert(the_worker == NULL); @@ -2036,7 +2199,6 @@ uv_loop_t *loop = uv_default_loop(); worker->loop = loop; - worker->id = worker_id; worker->count = worker_count; /* Register table for worker per-request variables */ @@ -2056,9 +2218,20 @@ /* Set some worker.* fields in Lua */ lua_getglobal(engine->L, "worker"); - lua_pushnumber(engine->L, worker_id); + pid_t pid = getpid(); + + auto_free char *pid_str = NULL; + const char *inst_name = getenv("SYSTEMD_INSTANCE"); + if (inst_name) { + lua_pushstring(engine->L, inst_name); + } else { + ret = asprintf(&pid_str, "%ld", (long)pid); + assert(ret > 0); + lua_pushstring(engine->L, pid_str); + } lua_setfield(engine->L, -2, "id"); - lua_pushnumber(engine->L, getpid()); + + lua_pushnumber(engine->L, pid); lua_setfield(engine->L, -2, "pid"); lua_pushnumber(engine->L, worker_count); lua_setfield(engine->L, -2, "count"); @@ -2070,7 +2243,7 @@ the_worker = worker; loop->data = the_worker; - /* ^^^^ This shouldn't be used anymore, but it's hard to be 100% sure. */ + /* ^^^^ Now this shouldn't be used anymore, but it's hard to be 100% sure. */ return kr_ok(); } diff -Nru knot-resolver-5.1.1/daemon/worker.h knot-resolver-5.2.1/daemon/worker.h --- knot-resolver-5.1.1/daemon/worker.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/worker.h 2020-12-09 09:44:29.000000000 +0000 @@ -23,7 +23,7 @@ /** Create and initialize the worker. * \return error code (ENOMEM) */ -int worker_init(struct engine *engine, int worker_id, int worker_count); +int worker_init(struct engine *engine, int worker_count); /** Destroy the worker (free memory). */ void worker_deinit(void); @@ -31,12 +31,15 @@ /** * Process an incoming packet (query from a client or answer from upstream). * - * @param session session the packet came from - * @param peer address the packet came from - * @param query the packet, or NULL on an error from the transport layer + * @param session session the packet came from, or NULL (not from network) + * @param peer address the packet came from, or NULL (not from network) + * @param eth_* MAC addresses or NULL (they're useful for XDP) + * @param pkt the packet, or NULL (an error from the transport layer) * @return 0 or an error code */ -int worker_submit(struct session *session, const struct sockaddr *peer, knot_pkt_t *query); +int worker_submit(struct session *session, + const struct sockaddr *peer, const struct sockaddr *dst_addr, + const uint8_t *eth_from, const uint8_t *eth_to, knot_pkt_t *pkt); /** * End current DNS/TCP session, this disassociates pending tasks from this session @@ -108,7 +111,7 @@ bool worker_task_finished(struct qr_task *task); /** To be called after sending a DNS message. It mainly deals with cleanups. */ -int qr_task_on_send(struct qr_task *task, uv_handle_t *handle, int status); +int qr_task_on_send(struct qr_task *task, const uv_handle_t *handle, int status); /** Various worker statistics. Sync with wrk_stats() */ struct worker_stats { @@ -123,6 +126,11 @@ size_t tls; /**< Number of outbound queries over TLS. */ size_t ipv4; /**< Number of outbound queries over IPv4.*/ size_t ipv6; /**< Number of outbound queries over IPv6. */ + + size_t err_udp; /**< Total number of write errors for UDP transport. */ + size_t err_tcp; /**< Total number of write errors for TCP transport. */ + size_t err_tls; /**< Total number of write errors for TLS transport. */ + size_t err_http; /**< Total number of write errors for HTTP(S) transport. */ }; /** @cond internal */ @@ -147,8 +155,7 @@ struct worker_ctx { struct engine *engine; uv_loop_t *loop; - int id; - int count; + int count; /** unreliable, does not count systemd instance, do not use */ int vars_table_ref; unsigned tcp_pipeline_max; diff -Nru knot-resolver-5.1.1/daemon/zimport.c knot-resolver-5.2.1/daemon/zimport.c --- knot-resolver-5.1.1/daemon/zimport.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/daemon/zimport.c 2020-12-09 09:44:29.000000000 +0000 @@ -378,7 +378,7 @@ qry->zone_cut.key = z_import->key; qry->zone_cut.trust_anchor = z_import->ta; - if (knot_pkt_init_response(request->answer, query) != 0) { + if (knot_pkt_init_response(answer, query) != 0) { goto cleanup; } diff -Nru knot-resolver-5.1.1/debian/changelog knot-resolver-5.2.1/debian/changelog --- knot-resolver-5.1.1/debian/changelog 2020-06-30 22:28:40.000000000 +0000 +++ knot-resolver-5.2.1/debian/changelog 2020-12-15 08:56:25.000000000 +0000 @@ -1,14 +1,115 @@ -knot-resolver (5.1.1-0.1ubuntu1) groovy; urgency=medium +knot-resolver (5.2.1-1) unstable; urgency=medium - * d/control: - - knot-resolver-module-http Architecture can't be 'any' since it - doesn't build on arm64/s390x; change it to match archs from - main knot-resolver package - * d/t/control, d/t/roundtrip: - - pull test fixes from Debian WIP: - https://salsa.debian.org/dns-team/knot-resolver/-/merge_requests/4 + * New upstream release + * Remove no longer needed upstream patch: + 0001-fix-map-command-on-32-bit-platforms-regressed-in-5.2.patch + Note: it made the build non-reproducible. Thanks to Chris Lamb! + (Closes: #976827) + * Add lintian overrides for no-debconf-config tag and excessive line lengths + in some d/missing-sources/ .js files + * Fix no-symbols-control-file lintian override + * Update d/copyright: remove no longer included entries and reorder File + sections. + * Bump Standards-Version to 4.5.1. No changes required - -- Dan Streetman Tue, 30 Jun 2020 18:28:40 -0400 + -- Santiago Ruano Rincón Tue, 15 Dec 2020 09:56:25 +0100 + +knot-resolver (5.2.0-2) unstable; urgency=low + + * Team upload. + + [ Jakub Ružička ] + * bugfix release + * debian/patches: fix map() command on 32-bit + + [ Dan Streetman ] + * d/control: Change arch 'any' to specific list + + [ Santiago Ruano Rincón ] + * Upload to unstable + + -- Santiago Ruano Rincón Mon, 07 Dec 2020 17:29:18 +0100 + +knot-resolver (5.2.0-1) experimental; urgency=medium + + [ Jakub Ružička ] + * new upstream release + * use experimental in order to safely move to knot >= 3.0 + * debian/control: require libknot-dev >= 3.0.1 + * debian/control: add myself to Uploaders + * debian/control: add lua-basexx build dep + * debian/control: add libnghttp2 dep for DoH support + * debian/control: use HTTP instead of HTTP/2 in desc + * debian/not-installed: don't use sysusers + + [ Santiago Ruano Rincón ] + * Set RELEASE to experimental in debian/salsa-ci.yml + + -- Jakub Ružička Fri, 20 Nov 2020 21:41:22 +0100 + +knot-resolver (5.1.3-1) unstable; urgency=low + + * New upstream release. + * Add debconf template translations: + - German (Closes: #969234) + - Dutch (Closes: #969341) + - French (Closes: #969478) + * d/control: build again for arm64. See + https://gitlab.nic.cz/knot/knot-resolver/-/merge_requests/1033 + * d/rules: really disable config_test on arm64. enable on other archs + + -- Santiago Ruano Rincón Mon, 14 Sep 2020 11:24:10 +0200 + +knot-resolver (5.1.2-3) unstable; urgency=medium + + * d/tests/control: add skip-not-installable restriction to avoid failure + state on arm64 + + -- Santiago Ruano Rincón Wed, 12 Aug 2020 22:24:46 +0200 + +knot-resolver (5.1.2-2) unstable; urgency=low + + * Add myself to Uploaders + * debian/rules: disable config_tests on arm64 + https://gitlab.nic.cz/knot/knot-resolver/-/issues/593 + * knot-resolver-module-http: explicitly set non-arm64 architectures + + -- Santiago Ruano Rincón Fri, 31 Jul 2020 16:17:50 +0200 + +knot-resolver (5.1.2-1) unstable; urgency=low + + * Team upload. + * New upstream release. (Closes: #966077) + * preinst, postinst maintainer scripts: base upgrade-4-to-5-related code + from upstream distro/deb/. + * Update debian/copyright to include CZ.NIC in debian/ files + * Document changes and manual action required when upgrading from 3.x, in + debian/NEWS and through debconf. (Closes: #952673) + Thanks to Justin B Rye for reviewing the English template. + * Add Build-Dep on po-debconf + * Add debian/po + + -- Santiago Ruano Rincón Wed, 29 Jul 2020 17:07:51 +0200 + +knot-resolver (5.1.1-1) unstable; urgency=medium + + * Team upload. + * Acknowledge 5.1.1-0.1 NMU. Thanks to Daniel Baumann. + * Mitigates NXNSAttack (CVE-2020-12667) (Closes: #961076) + * d/tests/control: remove Test-Command: make -k installcheck ... It is + broken since meson+ninja is used to build kresd + * d/tests/roundtrip: update to 5.x: change --forks for --noninteractive + option. fix control socket path + * Add debian/salsa-ci.yml + * d/tests/control: set Restrictions: needs-root for roundtrip + * Add source of dygraph.js to debian/missing-sources + * update dygraph.min.js in debian/copyright, remove entry for + dygraph-combined.js + * remove debian/missing-sources/dygraph-combined.js + * Renable PIE. Building seems to be OK now. + * d/control: Bump debhelper-compat to 13 + + -- Santiago Ruano Rincón Fri, 10 Jul 2020 13:43:19 +0200 knot-resolver (5.1.1-0.1) unstable; urgency=medium diff -Nru knot-resolver-5.1.1/debian/control knot-resolver-5.2.1/debian/control --- knot-resolver-5.1.1/debian/control 2020-06-30 22:28:40.000000000 +0000 +++ knot-resolver-5.2.1/debian/control 2020-12-14 16:09:21.000000000 +0000 @@ -3,8 +3,10 @@ Priority: optional Maintainer: knot-resolver packagers Uploaders: Ondřej Surý , - Daniel Kahn Gillmor -Build-Depends: debhelper-compat (= 12), + Daniel Kahn Gillmor , + Santiago Ruano Rincón , + Jakub Ružička +Build-Depends: debhelper-compat (= 13), dns-root-data, doxygen, gnutls-bin , @@ -13,23 +15,26 @@ libedit-dev, libfstrm-dev, libgnutls28-dev, - libknot-dev (>= 2.8.0~), + libknot-dev (>= 3.0.1), liblmdb-dev, libluajit-5.1-dev, + libnghttp2-dev, libprotobuf-c-dev, libsystemd-dev (>= 227) [linux-any], libuv1-dev, + lua-basexx, lua-cqueues, luajit, meson, ninja-build, + po-debconf, pkg-config, protobuf-c-compiler, python3-breathe, python3-sphinx, python3-sphinx-rtd-theme, socat -Standards-Version: 4.3.0 +Standards-Version: 4.5.1 Homepage: https://www.knot-resolver.cz/ Vcs-Browser: https://salsa.debian.org/dns-team/knot-resolver Vcs-Git: https://salsa.debian.org/dns-team/knot-resolver.git @@ -38,10 +43,9 @@ Package: knot-resolver # Doesn't build on s390x due to missing luajit # https://github.com/LuaJIT/LuaJIT/issues/397 -# Doesn't build on arm64 due to luajit issue -# https://gitlab.nic.cz/knot/knot-resolver/issues/216 -Architecture: amd64 armel armhf i386 mips mips64el mipsel ppc64 ppc64el +Architecture: amd64 arm64 armel armhf i386 mips mips64el mipsel ppc64 ppc64el Depends: adduser, + debconf, dns-root-data, lua-sec, lua-socket, @@ -70,7 +74,7 @@ nodes depending on the contention without downtime. Package: knot-resolver-module-http -Architecture: amd64 armel armhf i386 mips mips64el mipsel ppc64 ppc64el +Architecture: amd64 arm64 armel armhf i386 mips mips64el mipsel ppc64 ppc64el Depends: fonts-glyphicons-halflings, libjs-bootstrap, libjs-d3, @@ -80,7 +84,7 @@ ${misc:Depends}, ${shlibs:Depends} Breaks: knot-resolver-module-tinyweb (<< 1.1.0~git20160713-1~) -Description: HTTP/2 module for Knot Resolver +Description: HTTP module for Knot Resolver The Knot Resolver is a caching full resolver implementation written in C and LuaJIT, including both a resolver library and a daemon. Modular architecture of the library keeps the core tiny and @@ -88,7 +92,7 @@ extensions. There are three built-in modules - iterator, cache, validator, and many external. . - This package contains HTTP/2 module for local visualization of the + This package contains HTTP module for local visualization of the resolver cache and queries. Package: knot-resolver-doc diff -Nru knot-resolver-5.1.1/debian/copyright knot-resolver-5.2.1/debian/copyright --- knot-resolver-5.1.1/debian/copyright 2020-02-28 09:10:07.000000000 +0000 +++ knot-resolver-5.2.1/debian/copyright 2020-12-14 16:09:21.000000000 +0000 @@ -11,40 +11,17 @@ License: Expat Files: contrib/ccan/compiler/* - contrib/ccan/ilog/* Copyright: Rusty Russell License: CC0 -Files: tests/config/tapered/* -Copyright: 2012-2017, Peter Aronoff -License: BSD-3-clause - -Files: contrib/lmdb/* -Copyright: 1999-2003 The OpenLDAP Foundation -License: OpenLDAP - -Files: tests/deckard/contrib/libfaketime/* -Copyright: 2003-2017 Wolfgang Hommel -License: GPL-2 - -Files: tests/deckard/contrib/libswrap/* -Copyright: 2005,2008 Jelmer Vernooij - 2006-2009 Stefan Metzmacher - 2013 Andreas Schneider -License: BSD-3-clause +Files: contrib/ccan/json/* +Copyright: 2011 Joey Adams +License: Expat Files: contrib/murmurhash3/* Copyright: Austin Appleby License: CC0-1.0 -Files: debian/missing-sources/dygraph-combined.js - modules/http/static/dygraph-combined.js -Copyright: 2006-2014 Dan Vanderkam - 2016 Paul Miller - 2011 Robert Konigsberg - 2013 David Eberlein -License: MIT - Files: contrib/ucw/* Copyright: 1997-2015 Martin Mares 2005-2014 Tomas Valla @@ -52,9 +29,19 @@ 2007-2015 Pavel Charvat License: LGPL-2.1 -Files: contrib/ccan/json/* -Copyright: 2011 Joey Adams -License: Expat +Files: debian/* +Copyright: 2015 Ondřej Surý + 2015-2019 CZ.NIC +License: GPL-3.0+ + +Files: debian/missing-sources/dygraph.js + modules/http/static/dygraph.min.js +Copyright: 2006-2017 Dan Vanderkam + 2016 Paul Miller + 2011 Robert Konigsberg + 2013 David Eberlein +License: MIT +Comment: upstream site: http://dygraphs.com/download.html Files: lib/generic/map.c lib/generic/map.h Copyright: Dan Bernstein @@ -94,15 +81,14 @@ 2011-2015 Twitter, Inc. License: Expat -Files: modules/http/static/selectize.bootstrap3.min.css - modules/http/static/selectize.min.css +Files: modules/http/static/selectize.bootstrap3.css modules/http/static/selectize.min.js Copyright: 2013–2015 Brian Reavis & contributors License: Apache-2.0 -Files: debian/* -Copyright: 2015 Ondřej Surý -License: GPL-3.0+ +Files: tests/config/tapered/* +Copyright: 2012-2017, Peter Aronoff +License: BSD-3-clause License: LGPL-2.1 This library is free software; you can redistribute it and/or modify @@ -335,7 +321,6 @@ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - License: GPL-2 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 @@ -394,48 +379,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -License: OpenLDAP - Redistribution and use of this software and associated documentation - ("Software"), with or without modification, are permitted provided - that the following conditions are met: - . - 1. Redistributions in source form must retain copyright statements - and notices, - . - 2. Redistributions in binary form must reproduce applicable copyright - statements and notices, this list of conditions, and the following - disclaimer in the documentation and/or other materials provided - with the distribution, and - . - 3. Redistributions must contain a verbatim copy of this document. - . - The OpenLDAP Foundation may revise this license from time to time. - Each revision is distinguished by a version number. You may use - this Software under terms of this license revision or under the - terms of any subsequent revision of the license. - . - THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS - CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S) - OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, - INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - . - The names of the authors and copyright holders must not be used in - advertising or otherwise to promote the sale, use or other dealing - in this Software without specific, written prior permission. Title - to copyright in this Software shall at all times remain with copyright - holders. - . - OpenLDAP is a registered trademark of the OpenLDAP Foundation. - License: CC0-1.0 This work is licensed under the "Creative Commons Zero" license. . diff -Nru knot-resolver-5.1.1/debian/knot-resolver.lintian-overrides knot-resolver-5.2.1/debian/knot-resolver.lintian-overrides --- knot-resolver-5.1.1/debian/knot-resolver.lintian-overrides 2020-02-28 09:10:07.000000000 +0000 +++ knot-resolver-5.2.1/debian/knot-resolver.lintian-overrides 2020-12-14 16:09:21.000000000 +0000 @@ -4,4 +4,6 @@ knot-resolver: systemd-service-file-refers-to-unusual-wantedby-target lib/systemd/system/kresd@.service kresd.target # libkres9 is not currently functional independently. see https://bugs.debian.org/923970 knot-resolver: package-name-doesnt-match-sonames libkres9 -knot-resolver: no-symbols-control-file usr/lib/libkres.so.9 +knot-resolver: no-symbols-control-file usr/lib/*/libkres.so.9 +# Current use of debconf no required config - 2020-12-14 +knot-resolver: no-debconf-config diff -Nru knot-resolver-5.1.1/debian/knot-resolver.postinst knot-resolver-5.2.1/debian/knot-resolver.postinst --- knot-resolver-5.1.1/debian/knot-resolver.postinst 2020-02-28 09:10:07.000000000 +0000 +++ knot-resolver-5.2.1/debian/knot-resolver.postinst 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -#!/bin/sh -set -e - -if [ "$1" = "configure" ]; then - adduser --quiet --system --group --no-create-home --home /var/cache/knot-resolver knot-resolver -fi - -# Restart any running kresd instances if the root key is updated. -# Note: if knot-resolver upstream watches this file and reloads it -# upon a change, we can and should remove this trigger. -if [ "$1" = "triggered" ]; then - if [ "$2" = "/usr/share/dns/root.key" ]; then - # systemctl of the sub-services is the preferred method to restart - deb-systemd-invoke try-restart 'kresd@*.service' || true - # but if we are running sysvinit, we can try to restart that process anyway - # (kresd.service is masked on systems that use systemd) - invoke-rc.d kresd try-restart || true - fi -fi - -if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then - deb-systemd-invoke try-restart 'kresd@*.service' || true - invoke-rc.d kresd try-restart || true -fi - -#DEBHELPER# diff -Nru knot-resolver-5.1.1/debian/knot-resolver.postinst.in knot-resolver-5.2.1/debian/knot-resolver.postinst.in --- knot-resolver-5.1.1/debian/knot-resolver.postinst.in 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/debian/knot-resolver.postinst.in 2020-12-10 14:03:41.000000000 +0000 @@ -0,0 +1,43 @@ +#!/bin/sh +set -e + +# Source debconf library. +. /usr/share/debconf/confmodule + +# In Debian, this should be upgrade from 3-to-5, but the following code +# and the upgrade-4-to-5.lua file come from upstream. So I keep it like +# this for uniformity. Also, the issue relies on the lack of support on +# systemd sockets since 5.x. +# upgrade-4-to-5 +export UPG_DIR=/var/lib/knot-resolver/.upgrade-4-to-5 +if [ -f ${UPG_DIR}/.unfinished ] ; then + rm -f ${UPG_DIR}/.unfinished + kresd -c /usr/lib/@DEB_HOST_MULTIARCH@/knot-resolver/upgrade-4-to-5.lua >/dev/null 2>/dev/null + db_input high knot-resolver/upgrade-buster-to-bullseye || true + db_go || true + cat ${UPG_DIR}/kresd.conf.net 2>/dev/null +fi + +if [ "$1" = "configure" ]; then + adduser --quiet --system --group --no-create-home --home /var/cache/knot-resolver knot-resolver +fi + +# Restart any running kresd instances if the root key is updated. +# Note: if knot-resolver upstream watches this file and reloads it +# upon a change, we can and should remove this trigger. +if [ "$1" = "triggered" ]; then + if [ "$2" = "/usr/share/dns/root.key" ]; then + # systemctl of the sub-services is the preferred method to restart + deb-systemd-invoke try-restart 'kresd@*.service' || true + # but if we are running sysvinit, we can try to restart that process anyway + # (kresd.service is masked on systems that use systemd) + invoke-rc.d kresd try-restart || true + fi +fi + +if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then + deb-systemd-invoke try-restart 'kresd@*.service' || true + invoke-rc.d kresd try-restart || true +fi + +#DEBHELPER# diff -Nru knot-resolver-5.1.1/debian/knot-resolver.preinst knot-resolver-5.2.1/debian/knot-resolver.preinst --- knot-resolver-5.1.1/debian/knot-resolver.preinst 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/debian/knot-resolver.preinst 2020-12-10 14:03:41.000000000 +0000 @@ -0,0 +1,29 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +set -e + +# upgrade-4-to-5 +if [ -f /lib/systemd/system/kresd.socket ] ; then + export UPG_DIR=/var/lib/knot-resolver/.upgrade-4-to-5 + mkdir -p ${UPG_DIR} + touch ${UPG_DIR}/.unfinished + + for sock in kresd.socket kresd-tls.socket ; do + if deb-systemd-helper is-enabled ${sock} 2>/dev/null | grep -qv masked ; then + #TODO: handle systems not running systemd + systemctl show ${sock} -p Listen > ${UPG_DIR}/${sock} + case "$(systemctl show ${sock} -p BindIPv6Only)" in + *ipv6-only) + touch ${UPG_DIR}/${sock}.v6only + ;; + *default) + if cat /proc/sys/net/ipv6/bindv6only | grep -q 1 ; then + touch ${UPG_DIR}/${sock}.v6only + fi + ;; + esac + fi + done +fi + +#DEBHELPER# diff -Nru knot-resolver-5.1.1/debian/knot-resolver.templates knot-resolver-5.2.1/debian/knot-resolver.templates --- knot-resolver-5.1.1/debian/knot-resolver.templates 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/debian/knot-resolver.templates 2020-12-10 14:03:41.000000000 +0000 @@ -0,0 +1,17 @@ +Template: knot-resolver/upgrade-buster-to-bullseye +Type: note +_Description: Upgrading from Knot Resolver < 5.x + Knot Resolver configuration file requires manual upgrade. + . + Up to Knot Resolver 4.x, network interface bindings and service + start were done by default via systemd sockets. These systemd sockets + are no longer supported, and upgrading from a 3.x version (as in Debian + Buster) requires manual action to (re)enable the service. + . + Please refer to kresd.systemd(7) and to the + /usr/share/doc/knot-resolver/upgrading.html file (from + knot-resolver-doc). An online version of the latter is available at + https://knot-resolver.readthedocs.io/en/stable/upgrading.html#x-to-5-x + . + For convenience, a suggested networking configuration can be found in + the file /var/lib/knot-resolver/.upgrade-4-to-5/kresd.conf.net diff -Nru knot-resolver-5.1.1/debian/missing-sources/dygraph-combined.js knot-resolver-5.2.1/debian/missing-sources/dygraph-combined.js --- knot-resolver-5.1.1/debian/missing-sources/dygraph-combined.js 2020-02-28 09:10:07.000000000 +0000 +++ knot-resolver-5.2.1/debian/missing-sources/dygraph-combined.js 1970-01-01 00:00:00.000000000 +0000 @@ -1,9464 +0,0 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Dygraph = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 1) { - for (var i = 1; i < arguments.length; i++) { - args[i - 1] = arguments[i]; - } - } - queue.push(new Item(fun, args)); - if (queue.length === 1 && !draining) { - runTimeout(drainQueue); - } -}; - -// v8 likes predictible objects -function Item(fun, array) { - this.fun = fun; - this.array = array; -} -Item.prototype.run = function () { - this.fun.apply(null, this.array); -}; -process.title = 'browser'; -process.browser = true; -process.env = {}; -process.argv = []; -process.version = ''; // empty string to avoid regexp issues -process.versions = {}; - -function noop() {} - -process.on = noop; -process.addListener = noop; -process.once = noop; -process.off = noop; -process.removeListener = noop; -process.removeAllListeners = noop; -process.emit = noop; -process.prependListener = noop; -process.prependOnceListener = noop; - -process.listeners = function (name) { return [] } - -process.binding = function (name) { - throw new Error('process.binding is not supported'); -}; - -process.cwd = function () { return '/' }; -process.chdir = function (dir) { - throw new Error('process.chdir is not supported'); -}; -process.umask = function() { return 0; }; - -},{}],2:[function(require,module,exports){ -/** - * @license - * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/** - * @fileoverview DataHandler implementation for the custom bars option. - * @author David Eberlein (david.eberlein@ch.sauter-bc.com) - */ - -/*global Dygraph:false */ -"use strict"; - -Object.defineProperty(exports, '__esModule', { - value: true -}); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -var _bars = require('./bars'); - -var _bars2 = _interopRequireDefault(_bars); - -/** - * @constructor - * @extends Dygraph.DataHandlers.BarsHandler - */ -var CustomBarsHandler = function CustomBarsHandler() {}; - -CustomBarsHandler.prototype = new _bars2['default'](); - -/** @inheritDoc */ -CustomBarsHandler.prototype.extractSeries = function (rawData, i, options) { - // TODO(danvk): pre-allocate series here. - var series = []; - var x, y, point; - var logScale = options.get('logscale'); - for (var j = 0; j < rawData.length; j++) { - x = rawData[j][0]; - point = rawData[j][i]; - if (logScale && point !== null) { - // On the log scale, points less than zero do not exist. - // This will create a gap in the chart. - if (point[0] <= 0 || point[1] <= 0 || point[2] <= 0) { - point = null; - } - } - // Extract to the unified data format. - if (point !== null) { - y = point[1]; - if (y !== null && !isNaN(y)) { - series.push([x, y, [point[0], point[2]]]); - } else { - series.push([x, y, [y, y]]); - } - } else { - series.push([x, null, [null, null]]); - } - } - return series; -}; - -/** @inheritDoc */ -CustomBarsHandler.prototype.rollingAverage = function (originalData, rollPeriod, options) { - rollPeriod = Math.min(rollPeriod, originalData.length); - var rollingData = []; - var y, low, high, mid, count, i, extremes; - - low = 0; - mid = 0; - high = 0; - count = 0; - for (i = 0; i < originalData.length; i++) { - y = originalData[i][1]; - extremes = originalData[i][2]; - rollingData[i] = originalData[i]; - - if (y !== null && !isNaN(y)) { - low += extremes[0]; - mid += y; - high += extremes[1]; - count += 1; - } - if (i - rollPeriod >= 0) { - var prev = originalData[i - rollPeriod]; - if (prev[1] !== null && !isNaN(prev[1])) { - low -= prev[2][0]; - mid -= prev[1]; - high -= prev[2][1]; - count -= 1; - } - } - if (count) { - rollingData[i] = [originalData[i][0], 1.0 * mid / count, [1.0 * low / count, 1.0 * high / count]]; - } else { - rollingData[i] = [originalData[i][0], null, [null, null]]; - } - } - - return rollingData; -}; - -exports['default'] = CustomBarsHandler; -module.exports = exports['default']; - -},{"./bars":5}],3:[function(require,module,exports){ -/** - * @license - * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/** - * @fileoverview DataHandler implementation for the error bars option. - * @author David Eberlein (david.eberlein@ch.sauter-bc.com) - */ - -/*global Dygraph:false */ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - -var _bars = require('./bars'); - -var _bars2 = _interopRequireDefault(_bars); - -/** - * @constructor - * @extends BarsHandler - */ -var ErrorBarsHandler = function ErrorBarsHandler() {}; - -ErrorBarsHandler.prototype = new _bars2["default"](); - -/** @inheritDoc */ -ErrorBarsHandler.prototype.extractSeries = function (rawData, i, options) { - // TODO(danvk): pre-allocate series here. - var series = []; - var x, y, variance, point; - var sigma = options.get("sigma"); - var logScale = options.get('logscale'); - for (var j = 0; j < rawData.length; j++) { - x = rawData[j][0]; - point = rawData[j][i]; - if (logScale && point !== null) { - // On the log scale, points less than zero do not exist. - // This will create a gap in the chart. - if (point[0] <= 0 || point[0] - sigma * point[1] <= 0) { - point = null; - } - } - // Extract to the unified data format. - if (point !== null) { - y = point[0]; - if (y !== null && !isNaN(y)) { - variance = sigma * point[1]; - // preserve original error value in extras for further - // filtering - series.push([x, y, [y - variance, y + variance, point[1]]]); - } else { - series.push([x, y, [y, y, y]]); - } - } else { - series.push([x, null, [null, null, null]]); - } - } - return series; -}; - -/** @inheritDoc */ -ErrorBarsHandler.prototype.rollingAverage = function (originalData, rollPeriod, options) { - rollPeriod = Math.min(rollPeriod, originalData.length); - var rollingData = []; - var sigma = options.get("sigma"); - - var i, j, y, v, sum, num_ok, stddev, variance, value; - - // Calculate the rolling average for the first rollPeriod - 1 points - // where there is not enough data to roll over the full number of points - for (i = 0; i < originalData.length; i++) { - sum = 0; - variance = 0; - num_ok = 0; - for (j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) { - y = originalData[j][1]; - if (y === null || isNaN(y)) continue; - num_ok++; - sum += y; - variance += Math.pow(originalData[j][2][2], 2); - } - if (num_ok) { - stddev = Math.sqrt(variance) / num_ok; - value = sum / num_ok; - rollingData[i] = [originalData[i][0], value, [value - sigma * stddev, value + sigma * stddev]]; - } else { - // This explicitly preserves NaNs to aid with "independent - // series". - // See testRollingAveragePreservesNaNs. - v = rollPeriod == 1 ? originalData[i][1] : null; - rollingData[i] = [originalData[i][0], v, [v, v]]; - } - } - - return rollingData; -}; - -exports["default"] = ErrorBarsHandler; -module.exports = exports["default"]; - -},{"./bars":5}],4:[function(require,module,exports){ -/** - * @license - * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/** - * @fileoverview DataHandler implementation for the combination - * of error bars and fractions options. - * @author David Eberlein (david.eberlein@ch.sauter-bc.com) - */ - -/*global Dygraph:false */ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - -var _bars = require('./bars'); - -var _bars2 = _interopRequireDefault(_bars); - -/** - * @constructor - * @extends Dygraph.DataHandlers.BarsHandler - */ -var FractionsBarsHandler = function FractionsBarsHandler() {}; - -FractionsBarsHandler.prototype = new _bars2["default"](); - -/** @inheritDoc */ -FractionsBarsHandler.prototype.extractSeries = function (rawData, i, options) { - // TODO(danvk): pre-allocate series here. - var series = []; - var x, y, point, num, den, value, stddev, variance; - var mult = 100.0; - var sigma = options.get("sigma"); - var logScale = options.get('logscale'); - for (var j = 0; j < rawData.length; j++) { - x = rawData[j][0]; - point = rawData[j][i]; - if (logScale && point !== null) { - // On the log scale, points less than zero do not exist. - // This will create a gap in the chart. - if (point[0] <= 0 || point[1] <= 0) { - point = null; - } - } - // Extract to the unified data format. - if (point !== null) { - num = point[0]; - den = point[1]; - if (num !== null && !isNaN(num)) { - value = den ? num / den : 0.0; - stddev = den ? sigma * Math.sqrt(value * (1 - value) / den) : 1.0; - variance = mult * stddev; - y = mult * value; - // preserve original values in extras for further filtering - series.push([x, y, [y - variance, y + variance, num, den]]); - } else { - series.push([x, num, [num, num, num, den]]); - } - } else { - series.push([x, null, [null, null, null, null]]); - } - } - return series; -}; - -/** @inheritDoc */ -FractionsBarsHandler.prototype.rollingAverage = function (originalData, rollPeriod, options) { - rollPeriod = Math.min(rollPeriod, originalData.length); - var rollingData = []; - var sigma = options.get("sigma"); - var wilsonInterval = options.get("wilsonInterval"); - - var low, high, i, stddev; - var num = 0; - var den = 0; // numerator/denominator - var mult = 100.0; - for (i = 0; i < originalData.length; i++) { - num += originalData[i][2][2]; - den += originalData[i][2][3]; - if (i - rollPeriod >= 0) { - num -= originalData[i - rollPeriod][2][2]; - den -= originalData[i - rollPeriod][2][3]; - } - - var date = originalData[i][0]; - var value = den ? num / den : 0.0; - if (wilsonInterval) { - // For more details on this confidence interval, see: - // http://en.wikipedia.org/wiki/Binomial_confidence_interval - if (den) { - var p = value < 0 ? 0 : value, - n = den; - var pm = sigma * Math.sqrt(p * (1 - p) / n + sigma * sigma / (4 * n * n)); - var denom = 1 + sigma * sigma / den; - low = (p + sigma * sigma / (2 * den) - pm) / denom; - high = (p + sigma * sigma / (2 * den) + pm) / denom; - rollingData[i] = [date, p * mult, [low * mult, high * mult]]; - } else { - rollingData[i] = [date, 0, [0, 0]]; - } - } else { - stddev = den ? sigma * Math.sqrt(value * (1 - value) / den) : 1.0; - rollingData[i] = [date, mult * value, [mult * (value - stddev), mult * (value + stddev)]]; - } - } - - return rollingData; -}; - -exports["default"] = FractionsBarsHandler; -module.exports = exports["default"]; - -},{"./bars":5}],5:[function(require,module,exports){ -/** - * @license - * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/** - * @fileoverview DataHandler base implementation for the "bar" - * data formats. This implementation must be extended and the - * extractSeries and rollingAverage must be implemented. - * @author David Eberlein (david.eberlein@ch.sauter-bc.com) - */ - -/*global Dygraph:false */ -/*global DygraphLayout:false */ -"use strict"; - -Object.defineProperty(exports, '__esModule', { - value: true -}); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -var _datahandler = require('./datahandler'); - -var _datahandler2 = _interopRequireDefault(_datahandler); - -var _dygraphLayout = require('../dygraph-layout'); - -var _dygraphLayout2 = _interopRequireDefault(_dygraphLayout); - -/** - * @constructor - * @extends {Dygraph.DataHandler} - */ -var BarsHandler = function BarsHandler() { - _datahandler2['default'].call(this); -}; -BarsHandler.prototype = new _datahandler2['default'](); - -// TODO(danvk): figure out why the jsdoc has to be copy/pasted from superclass. -// (I get closure compiler errors if this isn't here.) -/** - * @override - * @param {!Array.} rawData The raw data passed into dygraphs where - * rawData[i] = [x,ySeries1,...,ySeriesN]. - * @param {!number} seriesIndex Index of the series to extract. All other - * series should be ignored. - * @param {!DygraphOptions} options Dygraph options. - * @return {Array.<[!number,?number,?]>} The series in the unified data format - * where series[i] = [x,y,{extras}]. - */ -BarsHandler.prototype.extractSeries = function (rawData, seriesIndex, options) { - // Not implemented here must be extended -}; - -/** - * @override - * @param {!Array.<[!number,?number,?]>} series The series in the unified - * data format where series[i] = [x,y,{extras}]. - * @param {!number} rollPeriod The number of points over which to average the data - * @param {!DygraphOptions} options The dygraph options. - * TODO(danvk): be more specific than "Array" here. - * @return {!Array.<[!number,?number,?]>} the rolled series. - */ -BarsHandler.prototype.rollingAverage = function (series, rollPeriod, options) { - // Not implemented here, must be extended. -}; - -/** @inheritDoc */ -BarsHandler.prototype.onPointsCreated_ = function (series, points) { - for (var i = 0; i < series.length; ++i) { - var item = series[i]; - var point = points[i]; - point.y_top = NaN; - point.y_bottom = NaN; - point.yval_minus = _datahandler2['default'].parseFloat(item[2][0]); - point.yval_plus = _datahandler2['default'].parseFloat(item[2][1]); - } -}; - -/** @inheritDoc */ -BarsHandler.prototype.getExtremeYValues = function (series, dateWindow, options) { - var minY = null, - maxY = null, - y; - - var firstIdx = 0; - var lastIdx = series.length - 1; - - for (var j = firstIdx; j <= lastIdx; j++) { - y = series[j][1]; - if (y === null || isNaN(y)) continue; - - var low = series[j][2][0]; - var high = series[j][2][1]; - - if (low > y) low = y; // this can happen with custom bars, - if (high < y) high = y; // e.g. in tests/custom-bars.html - - if (maxY === null || high > maxY) maxY = high; - if (minY === null || low < minY) minY = low; - } - - return [minY, maxY]; -}; - -/** @inheritDoc */ -BarsHandler.prototype.onLineEvaluated = function (points, axis, logscale) { - var point; - for (var j = 0; j < points.length; j++) { - // Copy over the error terms - point = points[j]; - point.y_top = _dygraphLayout2['default'].calcYNormal_(axis, point.yval_minus, logscale); - point.y_bottom = _dygraphLayout2['default'].calcYNormal_(axis, point.yval_plus, logscale); - } -}; - -exports['default'] = BarsHandler; -module.exports = exports['default']; - -},{"../dygraph-layout":13,"./datahandler":6}],6:[function(require,module,exports){ -/** - * @license - * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/** - * @fileoverview This file contains the managment of data handlers - * @author David Eberlein (david.eberlein@ch.sauter-bc.com) - * - * The idea is to define a common, generic data format that works for all data - * structures supported by dygraphs. To make this possible, the DataHandler - * interface is introduced. This makes it possible, that dygraph itself can work - * with the same logic for every data type independent of the actual format and - * the DataHandler takes care of the data format specific jobs. - * DataHandlers are implemented for all data types supported by Dygraphs and - * return Dygraphs compliant formats. - * By default the correct DataHandler is chosen based on the options set. - * Optionally the user may use his own DataHandler (similar to the plugin - * system). - * - * - * The unified data format returend by each handler is defined as so: - * series[n][point] = [x,y,(extras)] - * - * This format contains the common basis that is needed to draw a simple line - * series extended by optional extras for more complex graphing types. It - * contains a primitive x value as first array entry, a primitive y value as - * second array entry and an optional extras object for additional data needed. - * - * x must always be a number. - * y must always be a number, NaN of type number or null. - * extras is optional and must be interpreted by the DataHandler. It may be of - * any type. - * - * In practice this might look something like this: - * default: [x, yVal] - * errorBar / customBar: [x, yVal, [yTopVariance, yBottomVariance] ] - * - */ -/*global Dygraph:false */ -/*global DygraphLayout:false */ - -"use strict"; - -/** - * - * The data handler is responsible for all data specific operations. All of the - * series data it receives and returns is always in the unified data format. - * Initially the unified data is created by the extractSeries method - * @constructor - */ -Object.defineProperty(exports, "__esModule", { - value: true -}); -var DygraphDataHandler = function DygraphDataHandler() {}; - -var handler = DygraphDataHandler; - -/** - * X-value array index constant for unified data samples. - * @const - * @type {number} - */ -handler.X = 0; - -/** - * Y-value array index constant for unified data samples. - * @const - * @type {number} - */ -handler.Y = 1; - -/** - * Extras-value array index constant for unified data samples. - * @const - * @type {number} - */ -handler.EXTRAS = 2; - -/** - * Extracts one series from the raw data (a 2D array) into an array of the - * unified data format. - * This is where undesirable points (i.e. negative values on log scales and - * missing values through which we wish to connect lines) are dropped. - * TODO(danvk): the "missing values" bit above doesn't seem right. - * - * @param {!Array.} rawData The raw data passed into dygraphs where - * rawData[i] = [x,ySeries1,...,ySeriesN]. - * @param {!number} seriesIndex Index of the series to extract. All other - * series should be ignored. - * @param {!DygraphOptions} options Dygraph options. - * @return {Array.<[!number,?number,?]>} The series in the unified data format - * where series[i] = [x,y,{extras}]. - */ -handler.prototype.extractSeries = function (rawData, seriesIndex, options) {}; - -/** - * Converts a series to a Point array. The resulting point array must be - * returned in increasing order of idx property. - * - * @param {!Array.<[!number,?number,?]>} series The series in the unified - * data format where series[i] = [x,y,{extras}]. - * @param {!string} setName Name of the series. - * @param {!number} boundaryIdStart Index offset of the first point, equal to the - * number of skipped points left of the date window minimum (if any). - * @return {!Array.} List of points for this series. - */ -handler.prototype.seriesToPoints = function (series, setName, boundaryIdStart) { - // TODO(bhs): these loops are a hot-spot for high-point-count charts. In - // fact, - // on chrome+linux, they are 6 times more expensive than iterating through - // the - // points and drawing the lines. The brunt of the cost comes from allocating - // the |point| structures. - var points = []; - for (var i = 0; i < series.length; ++i) { - var item = series[i]; - var yraw = item[1]; - var yval = yraw === null ? null : handler.parseFloat(yraw); - var point = { - x: NaN, - y: NaN, - xval: handler.parseFloat(item[0]), - yval: yval, - name: setName, // TODO(danvk): is this really necessary? - idx: i + boundaryIdStart - }; - points.push(point); - } - this.onPointsCreated_(series, points); - return points; -}; - -/** - * Callback called for each series after the series points have been generated - * which will later be used by the plotters to draw the graph. - * Here data may be added to the seriesPoints which is needed by the plotters. - * The indexes of series and points are in sync meaning the original data - * sample for series[i] is points[i]. - * - * @param {!Array.<[!number,?number,?]>} series The series in the unified - * data format where series[i] = [x,y,{extras}]. - * @param {!Array.} points The corresponding points passed - * to the plotter. - * @protected - */ -handler.prototype.onPointsCreated_ = function (series, points) {}; - -/** - * Calculates the rolling average of a data set. - * - * @param {!Array.<[!number,?number,?]>} series The series in the unified - * data format where series[i] = [x,y,{extras}]. - * @param {!number} rollPeriod The number of points over which to average the data - * @param {!DygraphOptions} options The dygraph options. - * @return {!Array.<[!number,?number,?]>} the rolled series. - */ -handler.prototype.rollingAverage = function (series, rollPeriod, options) {}; - -/** - * Computes the range of the data series (including confidence intervals). - * - * @param {!Array.<[!number,?number,?]>} series The series in the unified - * data format where series[i] = [x, y, {extras}]. - * @param {!Array.} dateWindow The x-value range to display with - * the format: [min, max]. - * @param {!DygraphOptions} options The dygraph options. - * @return {Array.} The low and high extremes of the series in the - * given window with the format: [low, high]. - */ -handler.prototype.getExtremeYValues = function (series, dateWindow, options) {}; - -/** - * Callback called for each series after the layouting data has been - * calculated before the series is drawn. Here normalized positioning data - * should be calculated for the extras of each point. - * - * @param {!Array.} points The points passed to - * the plotter. - * @param {!Object} axis The axis on which the series will be plotted. - * @param {!boolean} logscale Weather or not to use a logscale. - */ -handler.prototype.onLineEvaluated = function (points, axis, logscale) {}; - -/** - * Optimized replacement for parseFloat, which was way too slow when almost - * all values were type number, with few edge cases, none of which were strings. - * @param {?number} val - * @return {number} - * @protected - */ -handler.parseFloat = function (val) { - // parseFloat(null) is NaN - if (val === null) { - return NaN; - } - - // Assume it's a number or NaN. If it's something else, I'll be shocked. - return val; -}; - -exports["default"] = DygraphDataHandler; -module.exports = exports["default"]; - -},{}],7:[function(require,module,exports){ -/** - * @license - * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/** - * @fileoverview DataHandler implementation for the fractions option. - * @author David Eberlein (david.eberlein@ch.sauter-bc.com) - */ - -/*global Dygraph:false */ -"use strict"; - -Object.defineProperty(exports, '__esModule', { - value: true -}); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -var _datahandler = require('./datahandler'); - -var _datahandler2 = _interopRequireDefault(_datahandler); - -var _default = require('./default'); - -var _default2 = _interopRequireDefault(_default); - -/** - * @extends DefaultHandler - * @constructor - */ -var DefaultFractionHandler = function DefaultFractionHandler() {}; - -DefaultFractionHandler.prototype = new _default2['default'](); - -DefaultFractionHandler.prototype.extractSeries = function (rawData, i, options) { - // TODO(danvk): pre-allocate series here. - var series = []; - var x, y, point, num, den, value; - var mult = 100.0; - var logScale = options.get('logscale'); - for (var j = 0; j < rawData.length; j++) { - x = rawData[j][0]; - point = rawData[j][i]; - if (logScale && point !== null) { - // On the log scale, points less than zero do not exist. - // This will create a gap in the chart. - if (point[0] <= 0 || point[1] <= 0) { - point = null; - } - } - // Extract to the unified data format. - if (point !== null) { - num = point[0]; - den = point[1]; - if (num !== null && !isNaN(num)) { - value = den ? num / den : 0.0; - y = mult * value; - // preserve original values in extras for further filtering - series.push([x, y, [num, den]]); - } else { - series.push([x, num, [num, den]]); - } - } else { - series.push([x, null, [null, null]]); - } - } - return series; -}; - -DefaultFractionHandler.prototype.rollingAverage = function (originalData, rollPeriod, options) { - rollPeriod = Math.min(rollPeriod, originalData.length); - var rollingData = []; - - var i; - var num = 0; - var den = 0; // numerator/denominator - var mult = 100.0; - for (i = 0; i < originalData.length; i++) { - num += originalData[i][2][0]; - den += originalData[i][2][1]; - if (i - rollPeriod >= 0) { - num -= originalData[i - rollPeriod][2][0]; - den -= originalData[i - rollPeriod][2][1]; - } - - var date = originalData[i][0]; - var value = den ? num / den : 0.0; - rollingData[i] = [date, mult * value]; - } - - return rollingData; -}; - -exports['default'] = DefaultFractionHandler; -module.exports = exports['default']; - -},{"./datahandler":6,"./default":8}],8:[function(require,module,exports){ -/** - * @license - * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/** - * @fileoverview DataHandler default implementation used for simple line charts. - * @author David Eberlein (david.eberlein@ch.sauter-bc.com) - */ - -/*global Dygraph:false */ -"use strict"; - -Object.defineProperty(exports, '__esModule', { - value: true -}); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -var _datahandler = require('./datahandler'); - -var _datahandler2 = _interopRequireDefault(_datahandler); - -/** - * @constructor - * @extends Dygraph.DataHandler - */ -var DefaultHandler = function DefaultHandler() {}; - -DefaultHandler.prototype = new _datahandler2['default'](); - -/** @inheritDoc */ -DefaultHandler.prototype.extractSeries = function (rawData, i, options) { - // TODO(danvk): pre-allocate series here. - var series = []; - var logScale = options.get('logscale'); - for (var j = 0; j < rawData.length; j++) { - var x = rawData[j][0]; - var point = rawData[j][i]; - if (logScale) { - // On the log scale, points less than zero do not exist. - // This will create a gap in the chart. - if (point <= 0) { - point = null; - } - } - series.push([x, point]); - } - return series; -}; - -/** @inheritDoc */ -DefaultHandler.prototype.rollingAverage = function (originalData, rollPeriod, options) { - rollPeriod = Math.min(rollPeriod, originalData.length); - var rollingData = []; - - var i, j, y, sum, num_ok; - // Calculate the rolling average for the first rollPeriod - 1 points - // where - // there is not enough data to roll over the full number of points - if (rollPeriod == 1) { - return originalData; - } - for (i = 0; i < originalData.length; i++) { - sum = 0; - num_ok = 0; - for (j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) { - y = originalData[j][1]; - if (y === null || isNaN(y)) continue; - num_ok++; - sum += originalData[j][1]; - } - if (num_ok) { - rollingData[i] = [originalData[i][0], sum / num_ok]; - } else { - rollingData[i] = [originalData[i][0], null]; - } - } - - return rollingData; -}; - -/** @inheritDoc */ -DefaultHandler.prototype.getExtremeYValues = function (series, dateWindow, options) { - var minY = null, - maxY = null, - y; - var firstIdx = 0, - lastIdx = series.length - 1; - - for (var j = firstIdx; j <= lastIdx; j++) { - y = series[j][1]; - if (y === null || isNaN(y)) continue; - if (maxY === null || y > maxY) { - maxY = y; - } - if (minY === null || y < minY) { - minY = y; - } - } - return [minY, maxY]; -}; - -exports['default'] = DefaultHandler; -module.exports = exports['default']; - -},{"./datahandler":6}],9:[function(require,module,exports){ -/** - * @license - * Copyright 2006 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/** - * @fileoverview Based on PlotKit.CanvasRenderer, but modified to meet the - * needs of dygraphs. - * - * In particular, support for: - * - grid overlays - * - error bars - * - dygraphs attribute system - */ - -/** - * The DygraphCanvasRenderer class does the actual rendering of the chart onto - * a canvas. It's based on PlotKit.CanvasRenderer. - * @param {Object} element The canvas to attach to - * @param {Object} elementContext The 2d context of the canvas (injected so it - * can be mocked for testing.) - * @param {Layout} layout The DygraphLayout object for this graph. - * @constructor - */ - -/*global Dygraph:false */ -"use strict"; - -Object.defineProperty(exports, '__esModule', { - value: true -}); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -var _dygraphUtils = require('./dygraph-utils'); - -var utils = _interopRequireWildcard(_dygraphUtils); - -var _dygraph = require('./dygraph'); - -var _dygraph2 = _interopRequireDefault(_dygraph); - -/** - * @constructor - * - * This gets called when there are "new points" to chart. This is generally the - * case when the underlying data being charted has changed. It is _not_ called - * in the common case that the user has zoomed or is panning the view. - * - * The chart canvas has already been created by the Dygraph object. The - * renderer simply gets a drawing context. - * - * @param {Dygraph} dygraph The chart to which this renderer belongs. - * @param {HTMLCanvasElement} element The <canvas> DOM element on which to draw. - * @param {CanvasRenderingContext2D} elementContext The drawing context. - * @param {DygraphLayout} layout The chart's DygraphLayout object. - * - * TODO(danvk): remove the elementContext property. - */ -var DygraphCanvasRenderer = function DygraphCanvasRenderer(dygraph, element, elementContext, layout) { - this.dygraph_ = dygraph; - - this.layout = layout; - this.element = element; - this.elementContext = elementContext; - - this.height = dygraph.height_; - this.width = dygraph.width_; - - // --- check whether everything is ok before we return - if (!utils.isCanvasSupported(this.element)) { - throw "Canvas is not supported."; - } - - // internal state - this.area = layout.getPlotArea(); - - // Set up a clipping area for the canvas (and the interaction canvas). - // This ensures that we don't overdraw. - var ctx = this.dygraph_.canvas_ctx_; - ctx.beginPath(); - ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h); - ctx.clip(); - - ctx = this.dygraph_.hidden_ctx_; - ctx.beginPath(); - ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h); - ctx.clip(); -}; - -/** - * Clears out all chart content and DOM elements. - * This is called immediately before render() on every frame, including - * during zooms and pans. - * @private - */ -DygraphCanvasRenderer.prototype.clear = function () { - this.elementContext.clearRect(0, 0, this.width, this.height); -}; - -/** - * This method is responsible for drawing everything on the chart, including - * lines, error bars, fills and axes. - * It is called immediately after clear() on every frame, including during pans - * and zooms. - * @private - */ -DygraphCanvasRenderer.prototype.render = function () { - // attaches point.canvas{x,y} - this._updatePoints(); - - // actually draws the chart. - this._renderLineChart(); -}; - -/** - * Returns a predicate to be used with an iterator, which will - * iterate over points appropriately, depending on whether - * connectSeparatedPoints is true. When it's false, the predicate will - * skip over points with missing yVals. - */ -DygraphCanvasRenderer._getIteratorPredicate = function (connectSeparatedPoints) { - return connectSeparatedPoints ? DygraphCanvasRenderer._predicateThatSkipsEmptyPoints : null; -}; - -DygraphCanvasRenderer._predicateThatSkipsEmptyPoints = function (array, idx) { - return array[idx].yval !== null; -}; - -/** - * Draws a line with the styles passed in and calls all the drawPointCallbacks. - * @param {Object} e The dictionary passed to the plotter function. - * @private - */ -DygraphCanvasRenderer._drawStyledLine = function (e, color, strokeWidth, strokePattern, drawPoints, drawPointCallback, pointSize) { - var g = e.dygraph; - // TODO(konigsberg): Compute attributes outside this method call. - var stepPlot = g.getBooleanOption("stepPlot", e.setName); - - if (!utils.isArrayLike(strokePattern)) { - strokePattern = null; - } - - var drawGapPoints = g.getBooleanOption('drawGapEdgePoints', e.setName); - - var points = e.points; - var setName = e.setName; - var iter = utils.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate(g.getBooleanOption("connectSeparatedPoints", setName))); - - var stroking = strokePattern && strokePattern.length >= 2; - - var ctx = e.drawingContext; - ctx.save(); - if (stroking) { - if (ctx.setLineDash) ctx.setLineDash(strokePattern); - } - - var pointsOnLine = DygraphCanvasRenderer._drawSeries(e, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color); - DygraphCanvasRenderer._drawPointsOnLine(e, pointsOnLine, drawPointCallback, color, pointSize); - - if (stroking) { - if (ctx.setLineDash) ctx.setLineDash([]); - } - - ctx.restore(); -}; - -/** - * This does the actual drawing of lines on the canvas, for just one series. - * Returns a list of [canvasx, canvasy] pairs for points for which a - * drawPointCallback should be fired. These include isolated points, or all - * points if drawPoints=true. - * @param {Object} e The dictionary passed to the plotter function. - * @private - */ -DygraphCanvasRenderer._drawSeries = function (e, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color) { - - var prevCanvasX = null; - var prevCanvasY = null; - var nextCanvasY = null; - var isIsolated; // true if this point is isolated (no line segments) - var point; // the point being processed in the while loop - var pointsOnLine = []; // Array of [canvasx, canvasy] pairs. - var first = true; // the first cycle through the while loop - - var ctx = e.drawingContext; - ctx.beginPath(); - ctx.strokeStyle = color; - ctx.lineWidth = strokeWidth; - - // NOTE: we break the iterator's encapsulation here for about a 25% speedup. - var arr = iter.array_; - var limit = iter.end_; - var predicate = iter.predicate_; - - for (var i = iter.start_; i < limit; i++) { - point = arr[i]; - if (predicate) { - while (i < limit && !predicate(arr, i)) { - i++; - } - if (i == limit) break; - point = arr[i]; - } - - // FIXME: The 'canvasy != canvasy' test here catches NaN values but the test - // doesn't catch Infinity values. Could change this to - // !isFinite(point.canvasy), but I assume it avoids isNaN for performance? - if (point.canvasy === null || point.canvasy != point.canvasy) { - if (stepPlot && prevCanvasX !== null) { - // Draw a horizontal line to the start of the missing data - ctx.moveTo(prevCanvasX, prevCanvasY); - ctx.lineTo(point.canvasx, prevCanvasY); - } - prevCanvasX = prevCanvasY = null; - } else { - isIsolated = false; - if (drawGapPoints || prevCanvasX === null) { - iter.nextIdx_ = i; - iter.next(); - nextCanvasY = iter.hasNext ? iter.peek.canvasy : null; - - var isNextCanvasYNullOrNaN = nextCanvasY === null || nextCanvasY != nextCanvasY; - isIsolated = prevCanvasX === null && isNextCanvasYNullOrNaN; - if (drawGapPoints) { - // Also consider a point to be "isolated" if it's adjacent to a - // null point, excluding the graph edges. - if (!first && prevCanvasX === null || iter.hasNext && isNextCanvasYNullOrNaN) { - isIsolated = true; - } - } - } - - if (prevCanvasX !== null) { - if (strokeWidth) { - if (stepPlot) { - ctx.moveTo(prevCanvasX, prevCanvasY); - ctx.lineTo(point.canvasx, prevCanvasY); - } - - ctx.lineTo(point.canvasx, point.canvasy); - } - } else { - ctx.moveTo(point.canvasx, point.canvasy); - } - if (drawPoints || isIsolated) { - pointsOnLine.push([point.canvasx, point.canvasy, point.idx]); - } - prevCanvasX = point.canvasx; - prevCanvasY = point.canvasy; - } - first = false; - } - ctx.stroke(); - return pointsOnLine; -}; - -/** - * This fires the drawPointCallback functions, which draw dots on the points by - * default. This gets used when the "drawPoints" option is set, or when there - * are isolated points. - * @param {Object} e The dictionary passed to the plotter function. - * @private - */ -DygraphCanvasRenderer._drawPointsOnLine = function (e, pointsOnLine, drawPointCallback, color, pointSize) { - var ctx = e.drawingContext; - for (var idx = 0; idx < pointsOnLine.length; idx++) { - var cb = pointsOnLine[idx]; - ctx.save(); - drawPointCallback.call(e.dygraph, e.dygraph, e.setName, ctx, cb[0], cb[1], color, pointSize, cb[2]); - ctx.restore(); - } -}; - -/** - * Attaches canvas coordinates to the points array. - * @private - */ -DygraphCanvasRenderer.prototype._updatePoints = function () { - // Update Points - // TODO(danvk): here - // - // TODO(bhs): this loop is a hot-spot for high-point-count charts. These - // transformations can be pushed into the canvas via linear transformation - // matrices. - // NOTE(danvk): this is trickier than it sounds at first. The transformation - // needs to be done before the .moveTo() and .lineTo() calls, but must be - // undone before the .stroke() call to ensure that the stroke width is - // unaffected. An alternative is to reduce the stroke width in the - // transformed coordinate space, but you can't specify different values for - // each dimension (as you can with .scale()). The speedup here is ~12%. - var sets = this.layout.points; - for (var i = sets.length; i--;) { - var points = sets[i]; - for (var j = points.length; j--;) { - var point = points[j]; - point.canvasx = this.area.w * point.x + this.area.x; - point.canvasy = this.area.h * point.y + this.area.y; - } - } -}; - -/** - * Add canvas Actually draw the lines chart, including error bars. - * - * This function can only be called if DygraphLayout's points array has been - * updated with canvas{x,y} attributes, i.e. by - * DygraphCanvasRenderer._updatePoints. - * - * @param {string=} opt_seriesName when specified, only that series will - * be drawn. (This is used for expedited redrawing with highlightSeriesOpts) - * @param {CanvasRenderingContext2D} opt_ctx when specified, the drawing - * context. However, lines are typically drawn on the object's - * elementContext. - * @private - */ -DygraphCanvasRenderer.prototype._renderLineChart = function (opt_seriesName, opt_ctx) { - var ctx = opt_ctx || this.elementContext; - var i; - - var sets = this.layout.points; - var setNames = this.layout.setNames; - var setName; - - this.colors = this.dygraph_.colorsMap_; - - // Determine which series have specialized plotters. - var plotter_attr = this.dygraph_.getOption("plotter"); - var plotters = plotter_attr; - if (!utils.isArrayLike(plotters)) { - plotters = [plotters]; - } - - var setPlotters = {}; // series name -> plotter fn. - for (i = 0; i < setNames.length; i++) { - setName = setNames[i]; - var setPlotter = this.dygraph_.getOption("plotter", setName); - if (setPlotter == plotter_attr) continue; // not specialized. - - setPlotters[setName] = setPlotter; - } - - for (i = 0; i < plotters.length; i++) { - var plotter = plotters[i]; - var is_last = i == plotters.length - 1; - - for (var j = 0; j < sets.length; j++) { - setName = setNames[j]; - if (opt_seriesName && setName != opt_seriesName) continue; - - var points = sets[j]; - - // Only throw in the specialized plotters on the last iteration. - var p = plotter; - if (setName in setPlotters) { - if (is_last) { - p = setPlotters[setName]; - } else { - // Don't use the standard plotters in this case. - continue; - } - } - - var color = this.colors[setName]; - var strokeWidth = this.dygraph_.getOption("strokeWidth", setName); - - ctx.save(); - ctx.strokeStyle = color; - ctx.lineWidth = strokeWidth; - p({ - points: points, - setName: setName, - drawingContext: ctx, - color: color, - strokeWidth: strokeWidth, - dygraph: this.dygraph_, - axis: this.dygraph_.axisPropertiesForSeries(setName), - plotArea: this.area, - seriesIndex: j, - seriesCount: sets.length, - singleSeriesName: opt_seriesName, - allSeriesPoints: sets - }); - ctx.restore(); - } - } -}; - -/** - * Standard plotters. These may be used by clients via Dygraph.Plotters. - * See comments there for more details. - */ -DygraphCanvasRenderer._Plotters = { - linePlotter: function linePlotter(e) { - DygraphCanvasRenderer._linePlotter(e); - }, - - fillPlotter: function fillPlotter(e) { - DygraphCanvasRenderer._fillPlotter(e); - }, - - errorPlotter: function errorPlotter(e) { - DygraphCanvasRenderer._errorPlotter(e); - } -}; - -/** - * Plotter which draws the central lines for a series. - * @private - */ -DygraphCanvasRenderer._linePlotter = function (e) { - var g = e.dygraph; - var setName = e.setName; - var strokeWidth = e.strokeWidth; - - // TODO(danvk): Check if there's any performance impact of just calling - // getOption() inside of _drawStyledLine. Passing in so many parameters makes - // this code a bit nasty. - var borderWidth = g.getNumericOption("strokeBorderWidth", setName); - var drawPointCallback = g.getOption("drawPointCallback", setName) || utils.Circles.DEFAULT; - var strokePattern = g.getOption("strokePattern", setName); - var drawPoints = g.getBooleanOption("drawPoints", setName); - var pointSize = g.getNumericOption("pointSize", setName); - - if (borderWidth && strokeWidth) { - DygraphCanvasRenderer._drawStyledLine(e, g.getOption("strokeBorderColor", setName), strokeWidth + 2 * borderWidth, strokePattern, drawPoints, drawPointCallback, pointSize); - } - - DygraphCanvasRenderer._drawStyledLine(e, e.color, strokeWidth, strokePattern, drawPoints, drawPointCallback, pointSize); -}; - -/** - * Draws the shaded error bars/confidence intervals for each series. - * This happens before the center lines are drawn, since the center lines - * need to be drawn on top of the error bars for all series. - * @private - */ -DygraphCanvasRenderer._errorPlotter = function (e) { - var g = e.dygraph; - var setName = e.setName; - var errorBars = g.getBooleanOption("errorBars") || g.getBooleanOption("customBars"); - if (!errorBars) return; - - var fillGraph = g.getBooleanOption("fillGraph", setName); - if (fillGraph) { - console.warn("Can't use fillGraph option with error bars"); - } - - var ctx = e.drawingContext; - var color = e.color; - var fillAlpha = g.getNumericOption('fillAlpha', setName); - var stepPlot = g.getBooleanOption("stepPlot", setName); - var points = e.points; - - var iter = utils.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate(g.getBooleanOption("connectSeparatedPoints", setName))); - - var newYs; - - // setup graphics context - var prevX = NaN; - var prevY = NaN; - var prevYs = [-1, -1]; - // should be same color as the lines but only 15% opaque. - var rgb = utils.toRGB_(color); - var err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')'; - ctx.fillStyle = err_color; - ctx.beginPath(); - - var isNullUndefinedOrNaN = function isNullUndefinedOrNaN(x) { - return x === null || x === undefined || isNaN(x); - }; - - while (iter.hasNext) { - var point = iter.next(); - if (!stepPlot && isNullUndefinedOrNaN(point.y) || stepPlot && !isNaN(prevY) && isNullUndefinedOrNaN(prevY)) { - prevX = NaN; - continue; - } - - newYs = [point.y_bottom, point.y_top]; - if (stepPlot) { - prevY = point.y; - } - - // The documentation specifically disallows nulls inside the point arrays, - // but in case it happens we should do something sensible. - if (isNaN(newYs[0])) newYs[0] = point.y; - if (isNaN(newYs[1])) newYs[1] = point.y; - - newYs[0] = e.plotArea.h * newYs[0] + e.plotArea.y; - newYs[1] = e.plotArea.h * newYs[1] + e.plotArea.y; - if (!isNaN(prevX)) { - if (stepPlot) { - ctx.moveTo(prevX, prevYs[0]); - ctx.lineTo(point.canvasx, prevYs[0]); - ctx.lineTo(point.canvasx, prevYs[1]); - } else { - ctx.moveTo(prevX, prevYs[0]); - ctx.lineTo(point.canvasx, newYs[0]); - ctx.lineTo(point.canvasx, newYs[1]); - } - ctx.lineTo(prevX, prevYs[1]); - ctx.closePath(); - } - prevYs = newYs; - prevX = point.canvasx; - } - ctx.fill(); -}; - -/** - * Proxy for CanvasRenderingContext2D which drops moveTo/lineTo calls which are - * superfluous. It accumulates all movements which haven't changed the x-value - * and only applies the two with the most extreme y-values. - * - * Calls to lineTo/moveTo must have non-decreasing x-values. - */ -DygraphCanvasRenderer._fastCanvasProxy = function (context) { - var pendingActions = []; // array of [type, x, y] tuples - var lastRoundedX = null; - var lastFlushedX = null; - - var LINE_TO = 1, - MOVE_TO = 2; - - var actionCount = 0; // number of moveTos and lineTos passed to context. - - // Drop superfluous motions - // Assumes all pendingActions have the same (rounded) x-value. - var compressActions = function compressActions(opt_losslessOnly) { - if (pendingActions.length <= 1) return; - - // Lossless compression: drop inconsequential moveTos. - for (var i = pendingActions.length - 1; i > 0; i--) { - var action = pendingActions[i]; - if (action[0] == MOVE_TO) { - var prevAction = pendingActions[i - 1]; - if (prevAction[1] == action[1] && prevAction[2] == action[2]) { - pendingActions.splice(i, 1); - } - } - } - - // Lossless compression: ... drop consecutive moveTos ... - for (var i = 0; i < pendingActions.length - 1;) /* incremented internally */{ - var action = pendingActions[i]; - if (action[0] == MOVE_TO && pendingActions[i + 1][0] == MOVE_TO) { - pendingActions.splice(i, 1); - } else { - i++; - } - } - - // Lossy compression: ... drop all but the extreme y-values ... - if (pendingActions.length > 2 && !opt_losslessOnly) { - // keep an initial moveTo, but drop all others. - var startIdx = 0; - if (pendingActions[0][0] == MOVE_TO) startIdx++; - var minIdx = null, - maxIdx = null; - for (var i = startIdx; i < pendingActions.length; i++) { - var action = pendingActions[i]; - if (action[0] != LINE_TO) continue; - if (minIdx === null && maxIdx === null) { - minIdx = i; - maxIdx = i; - } else { - var y = action[2]; - if (y < pendingActions[minIdx][2]) { - minIdx = i; - } else if (y > pendingActions[maxIdx][2]) { - maxIdx = i; - } - } - } - var minAction = pendingActions[minIdx], - maxAction = pendingActions[maxIdx]; - pendingActions.splice(startIdx, pendingActions.length - startIdx); - if (minIdx < maxIdx) { - pendingActions.push(minAction); - pendingActions.push(maxAction); - } else if (minIdx > maxIdx) { - pendingActions.push(maxAction); - pendingActions.push(minAction); - } else { - pendingActions.push(minAction); - } - } - }; - - var flushActions = function flushActions(opt_noLossyCompression) { - compressActions(opt_noLossyCompression); - for (var i = 0, len = pendingActions.length; i < len; i++) { - var action = pendingActions[i]; - if (action[0] == LINE_TO) { - context.lineTo(action[1], action[2]); - } else if (action[0] == MOVE_TO) { - context.moveTo(action[1], action[2]); - } - } - if (pendingActions.length) { - lastFlushedX = pendingActions[pendingActions.length - 1][1]; - } - actionCount += pendingActions.length; - pendingActions = []; - }; - - var addAction = function addAction(action, x, y) { - var rx = Math.round(x); - if (lastRoundedX === null || rx != lastRoundedX) { - // if there are large gaps on the x-axis, it's essential to keep the - // first and last point as well. - var hasGapOnLeft = lastRoundedX - lastFlushedX > 1, - hasGapOnRight = rx - lastRoundedX > 1, - hasGap = hasGapOnLeft || hasGapOnRight; - flushActions(hasGap); - lastRoundedX = rx; - } - pendingActions.push([action, x, y]); - }; - - return { - moveTo: function moveTo(x, y) { - addAction(MOVE_TO, x, y); - }, - lineTo: function lineTo(x, y) { - addAction(LINE_TO, x, y); - }, - - // for major operations like stroke/fill, we skip compression to ensure - // that there are no artifacts at the right edge. - stroke: function stroke() { - flushActions(true);context.stroke(); - }, - fill: function fill() { - flushActions(true);context.fill(); - }, - beginPath: function beginPath() { - flushActions(true);context.beginPath(); - }, - closePath: function closePath() { - flushActions(true);context.closePath(); - }, - - _count: function _count() { - return actionCount; - } - }; -}; - -/** - * Draws the shaded regions when "fillGraph" is set. Not to be confused with - * error bars. - * - * For stacked charts, it's more convenient to handle all the series - * simultaneously. So this plotter plots all the points on the first series - * it's asked to draw, then ignores all the other series. - * - * @private - */ -DygraphCanvasRenderer._fillPlotter = function (e) { - // Skip if we're drawing a single series for interactive highlight overlay. - if (e.singleSeriesName) return; - - // We'll handle all the series at once, not one-by-one. - if (e.seriesIndex !== 0) return; - - var g = e.dygraph; - var setNames = g.getLabels().slice(1); // remove x-axis - - // getLabels() includes names for invisible series, which are not included in - // allSeriesPoints. We remove those to make the two match. - // TODO(danvk): provide a simpler way to get this information. - for (var i = setNames.length; i >= 0; i--) { - if (!g.visibility()[i]) setNames.splice(i, 1); - } - - var anySeriesFilled = (function () { - for (var i = 0; i < setNames.length; i++) { - if (g.getBooleanOption("fillGraph", setNames[i])) return true; - } - return false; - })(); - - if (!anySeriesFilled) return; - - var area = e.plotArea; - var sets = e.allSeriesPoints; - var setCount = sets.length; - - var stackedGraph = g.getBooleanOption("stackedGraph"); - var colors = g.getColors(); - - // For stacked graphs, track the baseline for filling. - // - // The filled areas below graph lines are trapezoids with two - // vertical edges. The top edge is the line segment being drawn, and - // the baseline is the bottom edge. Each baseline corresponds to the - // top line segment from the previous stacked line. In the case of - // step plots, the trapezoids are rectangles. - var baseline = {}; - var currBaseline; - var prevStepPlot; // for different line drawing modes (line/step) per series - - // Helper function to trace a line back along the baseline. - var traceBackPath = function traceBackPath(ctx, baselineX, baselineY, pathBack) { - ctx.lineTo(baselineX, baselineY); - if (stackedGraph) { - for (var i = pathBack.length - 1; i >= 0; i--) { - var pt = pathBack[i]; - ctx.lineTo(pt[0], pt[1]); - } - } - }; - - // process sets in reverse order (needed for stacked graphs) - for (var setIdx = setCount - 1; setIdx >= 0; setIdx--) { - var ctx = e.drawingContext; - var setName = setNames[setIdx]; - if (!g.getBooleanOption('fillGraph', setName)) continue; - - var fillAlpha = g.getNumericOption('fillAlpha', setName); - var stepPlot = g.getBooleanOption('stepPlot', setName); - var color = colors[setIdx]; - var axis = g.axisPropertiesForSeries(setName); - var axisY = 1.0 + axis.minyval * axis.yscale; - if (axisY < 0.0) axisY = 0.0;else if (axisY > 1.0) axisY = 1.0; - axisY = area.h * axisY + area.y; - - var points = sets[setIdx]; - var iter = utils.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate(g.getBooleanOption("connectSeparatedPoints", setName))); - - // setup graphics context - var prevX = NaN; - var prevYs = [-1, -1]; - var newYs; - // should be same color as the lines but only 15% opaque. - var rgb = utils.toRGB_(color); - var err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')'; - ctx.fillStyle = err_color; - ctx.beginPath(); - var last_x, - is_first = true; - - // If the point density is high enough, dropping segments on their way to - // the canvas justifies the overhead of doing so. - if (points.length > 2 * g.width_ || _dygraph2['default'].FORCE_FAST_PROXY) { - ctx = DygraphCanvasRenderer._fastCanvasProxy(ctx); - } - - // For filled charts, we draw points from left to right, then back along - // the x-axis to complete a shape for filling. - // For stacked plots, this "back path" is a more complex shape. This array - // stores the [x, y] values needed to trace that shape. - var pathBack = []; - - // TODO(danvk): there are a lot of options at play in this loop. - // The logic would be much clearer if some (e.g. stackGraph and - // stepPlot) were split off into separate sub-plotters. - var point; - while (iter.hasNext) { - point = iter.next(); - if (!utils.isOK(point.y) && !stepPlot) { - traceBackPath(ctx, prevX, prevYs[1], pathBack); - pathBack = []; - prevX = NaN; - if (point.y_stacked !== null && !isNaN(point.y_stacked)) { - baseline[point.canvasx] = area.h * point.y_stacked + area.y; - } - continue; - } - if (stackedGraph) { - if (!is_first && last_x == point.xval) { - continue; - } else { - is_first = false; - last_x = point.xval; - } - - currBaseline = baseline[point.canvasx]; - var lastY; - if (currBaseline === undefined) { - lastY = axisY; - } else { - if (prevStepPlot) { - lastY = currBaseline[0]; - } else { - lastY = currBaseline; - } - } - newYs = [point.canvasy, lastY]; - - if (stepPlot) { - // Step plots must keep track of the top and bottom of - // the baseline at each point. - if (prevYs[0] === -1) { - baseline[point.canvasx] = [point.canvasy, axisY]; - } else { - baseline[point.canvasx] = [point.canvasy, prevYs[0]]; - } - } else { - baseline[point.canvasx] = point.canvasy; - } - } else { - if (isNaN(point.canvasy) && stepPlot) { - newYs = [area.y + area.h, axisY]; - } else { - newYs = [point.canvasy, axisY]; - } - } - if (!isNaN(prevX)) { - // Move to top fill point - if (stepPlot) { - ctx.lineTo(point.canvasx, prevYs[0]); - ctx.lineTo(point.canvasx, newYs[0]); - } else { - ctx.lineTo(point.canvasx, newYs[0]); - } - - // Record the baseline for the reverse path. - if (stackedGraph) { - pathBack.push([prevX, prevYs[1]]); - if (prevStepPlot && currBaseline) { - // Draw to the bottom of the baseline - pathBack.push([point.canvasx, currBaseline[1]]); - } else { - pathBack.push([point.canvasx, newYs[1]]); - } - } - } else { - ctx.moveTo(point.canvasx, newYs[1]); - ctx.lineTo(point.canvasx, newYs[0]); - } - prevYs = newYs; - prevX = point.canvasx; - } - prevStepPlot = stepPlot; - if (newYs && point) { - traceBackPath(ctx, point.canvasx, newYs[1], pathBack); - pathBack = []; - } - ctx.fill(); - } -}; - -exports['default'] = DygraphCanvasRenderer; -module.exports = exports['default']; - -},{"./dygraph":18,"./dygraph-utils":17}],10:[function(require,module,exports){ -'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true -}); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -var _dygraphTickers = require('./dygraph-tickers'); - -var DygraphTickers = _interopRequireWildcard(_dygraphTickers); - -var _dygraphInteractionModel = require('./dygraph-interaction-model'); - -var _dygraphInteractionModel2 = _interopRequireDefault(_dygraphInteractionModel); - -var _dygraphCanvas = require('./dygraph-canvas'); - -var _dygraphCanvas2 = _interopRequireDefault(_dygraphCanvas); - -var _dygraphUtils = require('./dygraph-utils'); - -var utils = _interopRequireWildcard(_dygraphUtils); - -// Default attribute values. -var DEFAULT_ATTRS = { - highlightCircleSize: 3, - highlightSeriesOpts: null, - highlightSeriesBackgroundAlpha: 0.5, - highlightSeriesBackgroundColor: 'rgb(255, 255, 255)', - - labelsSeparateLines: false, - labelsShowZeroValues: true, - labelsKMB: false, - labelsKMG2: false, - showLabelsOnHighlight: true, - - digitsAfterDecimal: 2, - maxNumberWidth: 6, - sigFigs: null, - - strokeWidth: 1.0, - strokeBorderWidth: 0, - strokeBorderColor: "white", - - axisTickSize: 3, - axisLabelFontSize: 14, - rightGap: 5, - - showRoller: false, - xValueParser: undefined, - - delimiter: ',', - - sigma: 2.0, - errorBars: false, - fractions: false, - wilsonInterval: true, // only relevant if fractions is true - customBars: false, - fillGraph: false, - fillAlpha: 0.15, - connectSeparatedPoints: false, - - stackedGraph: false, - stackedGraphNaNFill: 'all', - hideOverlayOnMouseOut: true, - - legend: 'onmouseover', - stepPlot: false, - xRangePad: 0, - yRangePad: null, - drawAxesAtZero: false, - - // Sizes of the various chart labels. - titleHeight: 28, - xLabelHeight: 18, - yLabelWidth: 18, - - axisLineColor: "black", - axisLineWidth: 0.3, - gridLineWidth: 0.3, - axisLabelWidth: 50, - gridLineColor: "rgb(128,128,128)", - - interactionModel: _dygraphInteractionModel2['default'].defaultModel, - animatedZooms: false, // (for now) - - // Range selector options - showRangeSelector: false, - rangeSelectorHeight: 40, - rangeSelectorPlotStrokeColor: "#808FAB", - rangeSelectorPlotFillGradientColor: "white", - rangeSelectorPlotFillColor: "#A7B1C4", - rangeSelectorBackgroundStrokeColor: "gray", - rangeSelectorBackgroundLineWidth: 1, - rangeSelectorPlotLineWidth: 1.5, - rangeSelectorForegroundStrokeColor: "black", - rangeSelectorForegroundLineWidth: 1, - rangeSelectorAlpha: 0.6, - showInRangeSelector: null, - - // The ordering here ensures that central lines always appear above any - // fill bars/error bars. - plotter: [_dygraphCanvas2['default']._fillPlotter, _dygraphCanvas2['default']._errorPlotter, _dygraphCanvas2['default']._linePlotter], - - plugins: [], - - // per-axis options - axes: { - x: { - pixelsPerLabel: 70, - axisLabelWidth: 60, - axisLabelFormatter: utils.dateAxisLabelFormatter, - valueFormatter: utils.dateValueFormatter, - drawGrid: true, - drawAxis: true, - independentTicks: true, - ticker: DygraphTickers.dateTicker - }, - y: { - axisLabelWidth: 50, - pixelsPerLabel: 30, - valueFormatter: utils.numberValueFormatter, - axisLabelFormatter: utils.numberAxisLabelFormatter, - drawGrid: true, - drawAxis: true, - independentTicks: true, - ticker: DygraphTickers.numericTicks - }, - y2: { - axisLabelWidth: 50, - pixelsPerLabel: 30, - valueFormatter: utils.numberValueFormatter, - axisLabelFormatter: utils.numberAxisLabelFormatter, - drawAxis: true, // only applies when there are two axes of data. - drawGrid: false, - independentTicks: false, - ticker: DygraphTickers.numericTicks - } - } -}; - -exports['default'] = DEFAULT_ATTRS; -module.exports = exports['default']; - -},{"./dygraph-canvas":9,"./dygraph-interaction-model":12,"./dygraph-tickers":16,"./dygraph-utils":17}],11:[function(require,module,exports){ -/** - * @license - * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/** - * @fileoverview A wrapper around the Dygraph class which implements the - * interface for a GViz (aka Google Visualization API) visualization. - * It is designed to be a drop-in replacement for Google's AnnotatedTimeline, - * so the documentation at - * http://code.google.com/apis/chart/interactive/docs/gallery/annotatedtimeline.html - * translates over directly. - * - * For a full demo, see: - * - http://dygraphs.com/tests/gviz.html - * - http://dygraphs.com/tests/annotation-gviz.html - */ - -/*global Dygraph:false */ -"use strict"; - -Object.defineProperty(exports, '__esModule', { - value: true -}); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -var _dygraph = require('./dygraph'); - -var _dygraph2 = _interopRequireDefault(_dygraph); - -/** - * A wrapper around Dygraph that implements the gviz API. - * @param {!HTMLDivElement} container The DOM object the visualization should - * live in. - * @constructor - */ -var GVizChart = function GVizChart(container) { - this.container = container; -}; - -/** - * @param {GVizDataTable} data - * @param {Object.<*>} options - */ -GVizChart.prototype.draw = function (data, options) { - // Clear out any existing dygraph. - // TODO(danvk): would it make more sense to simply redraw using the current - // date_graph object? - this.container.innerHTML = ''; - if (typeof this.date_graph != 'undefined') { - this.date_graph.destroy(); - } - - this.date_graph = new _dygraph2['default'](this.container, data, options); -}; - -/** - * Google charts compatible setSelection - * Only row selection is supported, all points in the row will be highlighted - * @param {Array.<{row:number}>} selection_array array of the selected cells - * @public - */ -GVizChart.prototype.setSelection = function (selection_array) { - var row = false; - if (selection_array.length) { - row = selection_array[0].row; - } - this.date_graph.setSelection(row); -}; - -/** - * Google charts compatible getSelection implementation - * @return {Array.<{row:number,column:number}>} array of the selected cells - * @public - */ -GVizChart.prototype.getSelection = function () { - var selection = []; - - var row = this.date_graph.getSelection(); - - if (row < 0) return selection; - - var points = this.date_graph.layout_.points; - for (var setIdx = 0; setIdx < points.length; ++setIdx) { - selection.push({ row: row, column: setIdx + 1 }); - } - - return selection; -}; - -exports['default'] = GVizChart; -module.exports = exports['default']; - -},{"./dygraph":18}],12:[function(require,module,exports){ -/** - * @license - * Copyright 2011 Robert Konigsberg (konigsberg@google.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/** - * @fileoverview The default interaction model for Dygraphs. This is kept out - * of dygraph.js for better navigability. - * @author Robert Konigsberg (konigsberg@google.com) - */ - -/*global Dygraph:false */ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj["default"] = obj; return newObj; } } - -var _dygraphUtils = require('./dygraph-utils'); - -var utils = _interopRequireWildcard(_dygraphUtils); - -/** - * You can drag this many pixels past the edge of the chart and still have it - * be considered a zoom. This makes it easier to zoom to the exact edge of the - * chart, a fairly common operation. - */ -var DRAG_EDGE_MARGIN = 100; - -/** - * A collection of functions to facilitate build custom interaction models. - * @class - */ -var DygraphInteraction = {}; - -/** - * Checks whether the beginning & ending of an event were close enough that it - * should be considered a click. If it should, dispatch appropriate events. - * Returns true if the event was treated as a click. - * - * @param {Event} event - * @param {Dygraph} g - * @param {Object} context - */ -DygraphInteraction.maybeTreatMouseOpAsClick = function (event, g, context) { - context.dragEndX = utils.dragGetX_(event, context); - context.dragEndY = utils.dragGetY_(event, context); - var regionWidth = Math.abs(context.dragEndX - context.dragStartX); - var regionHeight = Math.abs(context.dragEndY - context.dragStartY); - - if (regionWidth < 2 && regionHeight < 2 && g.lastx_ !== undefined && g.lastx_ != -1) { - DygraphInteraction.treatMouseOpAsClick(g, event, context); - } - - context.regionWidth = regionWidth; - context.regionHeight = regionHeight; -}; - -/** - * Called in response to an interaction model operation that - * should start the default panning behavior. - * - * It's used in the default callback for "mousedown" operations. - * Custom interaction model builders can use it to provide the default - * panning behavior. - * - * @param {Event} event the event object which led to the startPan call. - * @param {Dygraph} g The dygraph on which to act. - * @param {Object} context The dragging context object (with - * dragStartX/dragStartY/etc. properties). This function modifies the - * context. - */ -DygraphInteraction.startPan = function (event, g, context) { - var i, axis; - context.isPanning = true; - var xRange = g.xAxisRange(); - - if (g.getOptionForAxis("logscale", "x")) { - context.initialLeftmostDate = utils.log10(xRange[0]); - context.dateRange = utils.log10(xRange[1]) - utils.log10(xRange[0]); - } else { - context.initialLeftmostDate = xRange[0]; - context.dateRange = xRange[1] - xRange[0]; - } - context.xUnitsPerPixel = context.dateRange / (g.plotter_.area.w - 1); - - if (g.getNumericOption("panEdgeFraction")) { - var maxXPixelsToDraw = g.width_ * g.getNumericOption("panEdgeFraction"); - var xExtremes = g.xAxisExtremes(); // I REALLY WANT TO CALL THIS xTremes! - - var boundedLeftX = g.toDomXCoord(xExtremes[0]) - maxXPixelsToDraw; - var boundedRightX = g.toDomXCoord(xExtremes[1]) + maxXPixelsToDraw; - - var boundedLeftDate = g.toDataXCoord(boundedLeftX); - var boundedRightDate = g.toDataXCoord(boundedRightX); - context.boundedDates = [boundedLeftDate, boundedRightDate]; - - var boundedValues = []; - var maxYPixelsToDraw = g.height_ * g.getNumericOption("panEdgeFraction"); - - for (i = 0; i < g.axes_.length; i++) { - axis = g.axes_[i]; - var yExtremes = axis.extremeRange; - - var boundedTopY = g.toDomYCoord(yExtremes[0], i) + maxYPixelsToDraw; - var boundedBottomY = g.toDomYCoord(yExtremes[1], i) - maxYPixelsToDraw; - - var boundedTopValue = g.toDataYCoord(boundedTopY, i); - var boundedBottomValue = g.toDataYCoord(boundedBottomY, i); - - boundedValues[i] = [boundedTopValue, boundedBottomValue]; - } - context.boundedValues = boundedValues; - } - - // Record the range of each y-axis at the start of the drag. - // If any axis has a valueRange, then we want a 2D pan. - // We can't store data directly in g.axes_, because it does not belong to us - // and could change out from under us during a pan (say if there's a data - // update). - context.is2DPan = false; - context.axes = []; - for (i = 0; i < g.axes_.length; i++) { - axis = g.axes_[i]; - var axis_data = {}; - var yRange = g.yAxisRange(i); - // TODO(konigsberg): These values should be in |context|. - // In log scale, initialTopValue, dragValueRange and unitsPerPixel are log scale. - var logscale = g.attributes_.getForAxis("logscale", i); - if (logscale) { - axis_data.initialTopValue = utils.log10(yRange[1]); - axis_data.dragValueRange = utils.log10(yRange[1]) - utils.log10(yRange[0]); - } else { - axis_data.initialTopValue = yRange[1]; - axis_data.dragValueRange = yRange[1] - yRange[0]; - } - axis_data.unitsPerPixel = axis_data.dragValueRange / (g.plotter_.area.h - 1); - context.axes.push(axis_data); - - // While calculating axes, set 2dpan. - if (axis.valueRange) context.is2DPan = true; - } -}; - -/** - * Called in response to an interaction model operation that - * responds to an event that pans the view. - * - * It's used in the default callback for "mousemove" operations. - * Custom interaction model builders can use it to provide the default - * panning behavior. - * - * @param {Event} event the event object which led to the movePan call. - * @param {Dygraph} g The dygraph on which to act. - * @param {Object} context The dragging context object (with - * dragStartX/dragStartY/etc. properties). This function modifies the - * context. - */ -DygraphInteraction.movePan = function (event, g, context) { - context.dragEndX = utils.dragGetX_(event, context); - context.dragEndY = utils.dragGetY_(event, context); - - var minDate = context.initialLeftmostDate - (context.dragEndX - context.dragStartX) * context.xUnitsPerPixel; - if (context.boundedDates) { - minDate = Math.max(minDate, context.boundedDates[0]); - } - var maxDate = minDate + context.dateRange; - if (context.boundedDates) { - if (maxDate > context.boundedDates[1]) { - // Adjust minDate, and recompute maxDate. - minDate = minDate - (maxDate - context.boundedDates[1]); - maxDate = minDate + context.dateRange; - } - } - - if (g.getOptionForAxis("logscale", "x")) { - g.dateWindow_ = [Math.pow(utils.LOG_SCALE, minDate), Math.pow(utils.LOG_SCALE, maxDate)]; - } else { - g.dateWindow_ = [minDate, maxDate]; - } - - // y-axis scaling is automatic unless this is a full 2D pan. - if (context.is2DPan) { - - var pixelsDragged = context.dragEndY - context.dragStartY; - - // Adjust each axis appropriately. - for (var i = 0; i < g.axes_.length; i++) { - var axis = g.axes_[i]; - var axis_data = context.axes[i]; - var unitsDragged = pixelsDragged * axis_data.unitsPerPixel; - - var boundedValue = context.boundedValues ? context.boundedValues[i] : null; - - // In log scale, maxValue and minValue are the logs of those values. - var maxValue = axis_data.initialTopValue + unitsDragged; - if (boundedValue) { - maxValue = Math.min(maxValue, boundedValue[1]); - } - var minValue = maxValue - axis_data.dragValueRange; - if (boundedValue) { - if (minValue < boundedValue[0]) { - // Adjust maxValue, and recompute minValue. - maxValue = maxValue - (minValue - boundedValue[0]); - minValue = maxValue - axis_data.dragValueRange; - } - } - if (g.attributes_.getForAxis("logscale", i)) { - axis.valueRange = [Math.pow(utils.LOG_SCALE, minValue), Math.pow(utils.LOG_SCALE, maxValue)]; - } else { - axis.valueRange = [minValue, maxValue]; - } - } - } - - g.drawGraph_(false); -}; - -/** - * Called in response to an interaction model operation that - * responds to an event that ends panning. - * - * It's used in the default callback for "mouseup" operations. - * Custom interaction model builders can use it to provide the default - * panning behavior. - * - * @param {Event} event the event object which led to the endPan call. - * @param {Dygraph} g The dygraph on which to act. - * @param {Object} context The dragging context object (with - * dragStartX/dragStartY/etc. properties). This function modifies the - * context. - */ -DygraphInteraction.endPan = DygraphInteraction.maybeTreatMouseOpAsClick; - -/** - * Called in response to an interaction model operation that - * responds to an event that starts zooming. - * - * It's used in the default callback for "mousedown" operations. - * Custom interaction model builders can use it to provide the default - * zooming behavior. - * - * @param {Event} event the event object which led to the startZoom call. - * @param {Dygraph} g The dygraph on which to act. - * @param {Object} context The dragging context object (with - * dragStartX/dragStartY/etc. properties). This function modifies the - * context. - */ -DygraphInteraction.startZoom = function (event, g, context) { - context.isZooming = true; - context.zoomMoved = false; -}; - -/** - * Called in response to an interaction model operation that - * responds to an event that defines zoom boundaries. - * - * It's used in the default callback for "mousemove" operations. - * Custom interaction model builders can use it to provide the default - * zooming behavior. - * - * @param {Event} event the event object which led to the moveZoom call. - * @param {Dygraph} g The dygraph on which to act. - * @param {Object} context The dragging context object (with - * dragStartX/dragStartY/etc. properties). This function modifies the - * context. - */ -DygraphInteraction.moveZoom = function (event, g, context) { - context.zoomMoved = true; - context.dragEndX = utils.dragGetX_(event, context); - context.dragEndY = utils.dragGetY_(event, context); - - var xDelta = Math.abs(context.dragStartX - context.dragEndX); - var yDelta = Math.abs(context.dragStartY - context.dragEndY); - - // drag direction threshold for y axis is twice as large as x axis - context.dragDirection = xDelta < yDelta / 2 ? utils.VERTICAL : utils.HORIZONTAL; - - g.drawZoomRect_(context.dragDirection, context.dragStartX, context.dragEndX, context.dragStartY, context.dragEndY, context.prevDragDirection, context.prevEndX, context.prevEndY); - - context.prevEndX = context.dragEndX; - context.prevEndY = context.dragEndY; - context.prevDragDirection = context.dragDirection; -}; - -/** - * TODO(danvk): move this logic into dygraph.js - * @param {Dygraph} g - * @param {Event} event - * @param {Object} context - */ -DygraphInteraction.treatMouseOpAsClick = function (g, event, context) { - var clickCallback = g.getFunctionOption('clickCallback'); - var pointClickCallback = g.getFunctionOption('pointClickCallback'); - - var selectedPoint = null; - - // Find out if the click occurs on a point. - var closestIdx = -1; - var closestDistance = Number.MAX_VALUE; - - // check if the click was on a particular point. - for (var i = 0; i < g.selPoints_.length; i++) { - var p = g.selPoints_[i]; - var distance = Math.pow(p.canvasx - context.dragEndX, 2) + Math.pow(p.canvasy - context.dragEndY, 2); - if (!isNaN(distance) && (closestIdx == -1 || distance < closestDistance)) { - closestDistance = distance; - closestIdx = i; - } - } - - // Allow any click within two pixels of the dot. - var radius = g.getNumericOption('highlightCircleSize') + 2; - if (closestDistance <= radius * radius) { - selectedPoint = g.selPoints_[closestIdx]; - } - - if (selectedPoint) { - var e = { - cancelable: true, - point: selectedPoint, - canvasx: context.dragEndX, - canvasy: context.dragEndY - }; - var defaultPrevented = g.cascadeEvents_('pointClick', e); - if (defaultPrevented) { - // Note: this also prevents click / clickCallback from firing. - return; - } - if (pointClickCallback) { - pointClickCallback.call(g, event, selectedPoint); - } - } - - var e = { - cancelable: true, - xval: g.lastx_, // closest point by x value - pts: g.selPoints_, - canvasx: context.dragEndX, - canvasy: context.dragEndY - }; - if (!g.cascadeEvents_('click', e)) { - if (clickCallback) { - // TODO(danvk): pass along more info about the points, e.g. 'x' - clickCallback.call(g, event, g.lastx_, g.selPoints_); - } - } -}; - -/** - * Called in response to an interaction model operation that - * responds to an event that performs a zoom based on previously defined - * bounds.. - * - * It's used in the default callback for "mouseup" operations. - * Custom interaction model builders can use it to provide the default - * zooming behavior. - * - * @param {Event} event the event object which led to the endZoom call. - * @param {Dygraph} g The dygraph on which to end the zoom. - * @param {Object} context The dragging context object (with - * dragStartX/dragStartY/etc. properties). This function modifies the - * context. - */ -DygraphInteraction.endZoom = function (event, g, context) { - g.clearZoomRect_(); - context.isZooming = false; - DygraphInteraction.maybeTreatMouseOpAsClick(event, g, context); - - // The zoom rectangle is visibly clipped to the plot area, so its behavior - // should be as well. - // See http://code.google.com/p/dygraphs/issues/detail?id=280 - var plotArea = g.getArea(); - if (context.regionWidth >= 10 && context.dragDirection == utils.HORIZONTAL) { - var left = Math.min(context.dragStartX, context.dragEndX), - right = Math.max(context.dragStartX, context.dragEndX); - left = Math.max(left, plotArea.x); - right = Math.min(right, plotArea.x + plotArea.w); - if (left < right) { - g.doZoomX_(left, right); - } - context.cancelNextDblclick = true; - } else if (context.regionHeight >= 10 && context.dragDirection == utils.VERTICAL) { - var top = Math.min(context.dragStartY, context.dragEndY), - bottom = Math.max(context.dragStartY, context.dragEndY); - top = Math.max(top, plotArea.y); - bottom = Math.min(bottom, plotArea.y + plotArea.h); - if (top < bottom) { - g.doZoomY_(top, bottom); - } - context.cancelNextDblclick = true; - } - context.dragStartX = null; - context.dragStartY = null; -}; - -/** - * @private - */ -DygraphInteraction.startTouch = function (event, g, context) { - event.preventDefault(); // touch browsers are all nice. - if (event.touches.length > 1) { - // If the user ever puts two fingers down, it's not a double tap. - context.startTimeForDoubleTapMs = null; - } - - var touches = []; - for (var i = 0; i < event.touches.length; i++) { - var t = event.touches[i]; - // we dispense with 'dragGetX_' because all touchBrowsers support pageX - touches.push({ - pageX: t.pageX, - pageY: t.pageY, - dataX: g.toDataXCoord(t.pageX), - dataY: g.toDataYCoord(t.pageY) - // identifier: t.identifier - }); - } - context.initialTouches = touches; - - if (touches.length == 1) { - // This is just a swipe. - context.initialPinchCenter = touches[0]; - context.touchDirections = { x: true, y: true }; - } else if (touches.length >= 2) { - // It's become a pinch! - // In case there are 3+ touches, we ignore all but the "first" two. - - // only screen coordinates can be averaged (data coords could be log scale). - context.initialPinchCenter = { - pageX: 0.5 * (touches[0].pageX + touches[1].pageX), - pageY: 0.5 * (touches[0].pageY + touches[1].pageY), - - // TODO(danvk): remove - dataX: 0.5 * (touches[0].dataX + touches[1].dataX), - dataY: 0.5 * (touches[0].dataY + touches[1].dataY) - }; - - // Make pinches in a 45-degree swath around either axis 1-dimensional zooms. - var initialAngle = 180 / Math.PI * Math.atan2(context.initialPinchCenter.pageY - touches[0].pageY, touches[0].pageX - context.initialPinchCenter.pageX); - - // use symmetry to get it into the first quadrant. - initialAngle = Math.abs(initialAngle); - if (initialAngle > 90) initialAngle = 90 - initialAngle; - - context.touchDirections = { - x: initialAngle < 90 - 45 / 2, - y: initialAngle > 45 / 2 - }; - } - - // save the full x & y ranges. - context.initialRange = { - x: g.xAxisRange(), - y: g.yAxisRange() - }; -}; - -/** - * @private - */ -DygraphInteraction.moveTouch = function (event, g, context) { - // If the tap moves, then it's definitely not part of a double-tap. - context.startTimeForDoubleTapMs = null; - - var i, - touches = []; - for (i = 0; i < event.touches.length; i++) { - var t = event.touches[i]; - touches.push({ - pageX: t.pageX, - pageY: t.pageY - }); - } - var initialTouches = context.initialTouches; - - var c_now; - - // old and new centers. - var c_init = context.initialPinchCenter; - if (touches.length == 1) { - c_now = touches[0]; - } else { - c_now = { - pageX: 0.5 * (touches[0].pageX + touches[1].pageX), - pageY: 0.5 * (touches[0].pageY + touches[1].pageY) - }; - } - - // this is the "swipe" component - // we toss it out for now, but could use it in the future. - var swipe = { - pageX: c_now.pageX - c_init.pageX, - pageY: c_now.pageY - c_init.pageY - }; - var dataWidth = context.initialRange.x[1] - context.initialRange.x[0]; - var dataHeight = context.initialRange.y[0] - context.initialRange.y[1]; - swipe.dataX = swipe.pageX / g.plotter_.area.w * dataWidth; - swipe.dataY = swipe.pageY / g.plotter_.area.h * dataHeight; - var xScale, yScale; - - // The residual bits are usually split into scale & rotate bits, but we split - // them into x-scale and y-scale bits. - if (touches.length == 1) { - xScale = 1.0; - yScale = 1.0; - } else if (touches.length >= 2) { - var initHalfWidth = initialTouches[1].pageX - c_init.pageX; - xScale = (touches[1].pageX - c_now.pageX) / initHalfWidth; - - var initHalfHeight = initialTouches[1].pageY - c_init.pageY; - yScale = (touches[1].pageY - c_now.pageY) / initHalfHeight; - } - - // Clip scaling to [1/8, 8] to prevent too much blowup. - xScale = Math.min(8, Math.max(0.125, xScale)); - yScale = Math.min(8, Math.max(0.125, yScale)); - - var didZoom = false; - if (context.touchDirections.x) { - g.dateWindow_ = [c_init.dataX - swipe.dataX + (context.initialRange.x[0] - c_init.dataX) / xScale, c_init.dataX - swipe.dataX + (context.initialRange.x[1] - c_init.dataX) / xScale]; - didZoom = true; - } - - if (context.touchDirections.y) { - for (i = 0; i < 1 /*g.axes_.length*/; i++) { - var axis = g.axes_[i]; - var logscale = g.attributes_.getForAxis("logscale", i); - if (logscale) { - // TODO(danvk): implement - } else { - axis.valueRange = [c_init.dataY - swipe.dataY + (context.initialRange.y[0] - c_init.dataY) / yScale, c_init.dataY - swipe.dataY + (context.initialRange.y[1] - c_init.dataY) / yScale]; - didZoom = true; - } - } - } - - g.drawGraph_(false); - - // We only call zoomCallback on zooms, not pans, to mirror desktop behavior. - if (didZoom && touches.length > 1 && g.getFunctionOption('zoomCallback')) { - var viewWindow = g.xAxisRange(); - g.getFunctionOption("zoomCallback").call(g, viewWindow[0], viewWindow[1], g.yAxisRanges()); - } -}; - -/** - * @private - */ -DygraphInteraction.endTouch = function (event, g, context) { - if (event.touches.length !== 0) { - // this is effectively a "reset" - DygraphInteraction.startTouch(event, g, context); - } else if (event.changedTouches.length == 1) { - // Could be part of a "double tap" - // The heuristic here is that it's a double-tap if the two touchend events - // occur within 500ms and within a 50x50 pixel box. - var now = new Date().getTime(); - var t = event.changedTouches[0]; - if (context.startTimeForDoubleTapMs && now - context.startTimeForDoubleTapMs < 500 && context.doubleTapX && Math.abs(context.doubleTapX - t.screenX) < 50 && context.doubleTapY && Math.abs(context.doubleTapY - t.screenY) < 50) { - g.resetZoom(); - } else { - context.startTimeForDoubleTapMs = now; - context.doubleTapX = t.screenX; - context.doubleTapY = t.screenY; - } - } -}; - -// Determine the distance from x to [left, right]. -var distanceFromInterval = function distanceFromInterval(x, left, right) { - if (x < left) { - return left - x; - } else if (x > right) { - return x - right; - } else { - return 0; - } -}; - -/** - * Returns the number of pixels by which the event happens from the nearest - * edge of the chart. For events in the interior of the chart, this returns zero. - */ -var distanceFromChart = function distanceFromChart(event, g) { - var chartPos = utils.findPos(g.canvas_); - var box = { - left: chartPos.x, - right: chartPos.x + g.canvas_.offsetWidth, - top: chartPos.y, - bottom: chartPos.y + g.canvas_.offsetHeight - }; - - var pt = { - x: utils.pageX(event), - y: utils.pageY(event) - }; - - var dx = distanceFromInterval(pt.x, box.left, box.right), - dy = distanceFromInterval(pt.y, box.top, box.bottom); - return Math.max(dx, dy); -}; - -/** - * Default interation model for dygraphs. You can refer to specific elements of - * this when constructing your own interaction model, e.g.: - * g.updateOptions( { - * interactionModel: { - * mousedown: DygraphInteraction.defaultInteractionModel.mousedown - * } - * } ); - */ -DygraphInteraction.defaultModel = { - // Track the beginning of drag events - mousedown: function mousedown(event, g, context) { - // Right-click should not initiate a zoom. - if (event.button && event.button == 2) return; - - context.initializeMouseDown(event, g, context); - - if (event.altKey || event.shiftKey) { - DygraphInteraction.startPan(event, g, context); - } else { - DygraphInteraction.startZoom(event, g, context); - } - - // Note: we register mousemove/mouseup on document to allow some leeway for - // events to move outside of the chart. Interaction model events get - // registered on the canvas, which is too small to allow this. - var mousemove = function mousemove(event) { - if (context.isZooming) { - // When the mouse moves >200px from the chart edge, cancel the zoom. - var d = distanceFromChart(event, g); - if (d < DRAG_EDGE_MARGIN) { - DygraphInteraction.moveZoom(event, g, context); - } else { - if (context.dragEndX !== null) { - context.dragEndX = null; - context.dragEndY = null; - g.clearZoomRect_(); - } - } - } else if (context.isPanning) { - DygraphInteraction.movePan(event, g, context); - } - }; - var mouseup = function mouseup(event) { - if (context.isZooming) { - if (context.dragEndX !== null) { - DygraphInteraction.endZoom(event, g, context); - } else { - DygraphInteraction.maybeTreatMouseOpAsClick(event, g, context); - } - } else if (context.isPanning) { - DygraphInteraction.endPan(event, g, context); - } - - utils.removeEvent(document, 'mousemove', mousemove); - utils.removeEvent(document, 'mouseup', mouseup); - context.destroy(); - }; - - g.addAndTrackEvent(document, 'mousemove', mousemove); - g.addAndTrackEvent(document, 'mouseup', mouseup); - }, - willDestroyContextMyself: true, - - touchstart: function touchstart(event, g, context) { - DygraphInteraction.startTouch(event, g, context); - }, - touchmove: function touchmove(event, g, context) { - DygraphInteraction.moveTouch(event, g, context); - }, - touchend: function touchend(event, g, context) { - DygraphInteraction.endTouch(event, g, context); - }, - - // Disable zooming out if panning. - dblclick: function dblclick(event, g, context) { - if (context.cancelNextDblclick) { - context.cancelNextDblclick = false; - return; - } - - // Give plugins a chance to grab this event. - var e = { - canvasx: context.dragEndX, - canvasy: context.dragEndY, - cancelable: true - }; - if (g.cascadeEvents_('dblclick', e)) { - return; - } - - if (event.altKey || event.shiftKey) { - return; - } - g.resetZoom(); - } -}; - -/* -Dygraph.DEFAULT_ATTRS.interactionModel = DygraphInteraction.defaultModel; - -// old ways of accessing these methods/properties -Dygraph.defaultInteractionModel = DygraphInteraction.defaultModel; -Dygraph.endZoom = DygraphInteraction.endZoom; -Dygraph.moveZoom = DygraphInteraction.moveZoom; -Dygraph.startZoom = DygraphInteraction.startZoom; -Dygraph.endPan = DygraphInteraction.endPan; -Dygraph.movePan = DygraphInteraction.movePan; -Dygraph.startPan = DygraphInteraction.startPan; -*/ - -DygraphInteraction.nonInteractiveModel_ = { - mousedown: function mousedown(event, g, context) { - context.initializeMouseDown(event, g, context); - }, - mouseup: DygraphInteraction.maybeTreatMouseOpAsClick -}; - -// Default interaction model when using the range selector. -DygraphInteraction.dragIsPanInteractionModel = { - mousedown: function mousedown(event, g, context) { - context.initializeMouseDown(event, g, context); - DygraphInteraction.startPan(event, g, context); - }, - mousemove: function mousemove(event, g, context) { - if (context.isPanning) { - DygraphInteraction.movePan(event, g, context); - } - }, - mouseup: function mouseup(event, g, context) { - if (context.isPanning) { - DygraphInteraction.endPan(event, g, context); - } - } -}; - -exports["default"] = DygraphInteraction; -module.exports = exports["default"]; - -},{"./dygraph-utils":17}],13:[function(require,module,exports){ -/** - * @license - * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/** - * @fileoverview Based on PlotKitLayout, but modified to meet the needs of - * dygraphs. - */ - -/*global Dygraph:false */ -"use strict"; - -Object.defineProperty(exports, '__esModule', { - value: true -}); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -var _dygraphUtils = require('./dygraph-utils'); - -var utils = _interopRequireWildcard(_dygraphUtils); - -/** - * Creates a new DygraphLayout object. - * - * This class contains all the data to be charted. - * It uses data coordinates, but also records the chart range (in data - * coordinates) and hence is able to calculate percentage positions ('In this - * view, Point A lies 25% down the x-axis.') - * - * Two things that it does not do are: - * 1. Record pixel coordinates for anything. - * 2. (oddly) determine anything about the layout of chart elements. - * - * The naming is a vestige of Dygraph's original PlotKit roots. - * - * @constructor - */ -var DygraphLayout = function DygraphLayout(dygraph) { - this.dygraph_ = dygraph; - /** - * Array of points for each series. - * - * [series index][row index in series] = |Point| structure, - * where series index refers to visible series only, and the - * point index is for the reduced set of points for the current - * zoom region (including one point just outside the window). - * All points in the same row index share the same X value. - * - * @type {Array.>} - */ - this.points = []; - this.setNames = []; - this.annotations = []; - this.yAxes_ = null; - - // TODO(danvk): it's odd that xTicks_ and yTicks_ are inputs, but xticks and - // yticks are outputs. Clean this up. - this.xTicks_ = null; - this.yTicks_ = null; -}; - -/** - * Add points for a single series. - * - * @param {string} setname Name of the series. - * @param {Array.} set_xy Points for the series. - */ -DygraphLayout.prototype.addDataset = function (setname, set_xy) { - this.points.push(set_xy); - this.setNames.push(setname); -}; - -/** - * Returns the box which the chart should be drawn in. This is the canvas's - * box, less space needed for the axis and chart labels. - * - * @return {{x: number, y: number, w: number, h: number}} - */ -DygraphLayout.prototype.getPlotArea = function () { - return this.area_; -}; - -// Compute the box which the chart should be drawn in. This is the canvas's -// box, less space needed for axis, chart labels, and other plug-ins. -// NOTE: This should only be called by Dygraph.predraw_(). -DygraphLayout.prototype.computePlotArea = function () { - var area = { - // TODO(danvk): per-axis setting. - x: 0, - y: 0 - }; - - area.w = this.dygraph_.width_ - area.x - this.dygraph_.getOption('rightGap'); - area.h = this.dygraph_.height_; - - // Let plugins reserve space. - var e = { - chart_div: this.dygraph_.graphDiv, - reserveSpaceLeft: function reserveSpaceLeft(px) { - var r = { - x: area.x, - y: area.y, - w: px, - h: area.h - }; - area.x += px; - area.w -= px; - return r; - }, - reserveSpaceRight: function reserveSpaceRight(px) { - var r = { - x: area.x + area.w - px, - y: area.y, - w: px, - h: area.h - }; - area.w -= px; - return r; - }, - reserveSpaceTop: function reserveSpaceTop(px) { - var r = { - x: area.x, - y: area.y, - w: area.w, - h: px - }; - area.y += px; - area.h -= px; - return r; - }, - reserveSpaceBottom: function reserveSpaceBottom(px) { - var r = { - x: area.x, - y: area.y + area.h - px, - w: area.w, - h: px - }; - area.h -= px; - return r; - }, - chartRect: function chartRect() { - return { x: area.x, y: area.y, w: area.w, h: area.h }; - } - }; - this.dygraph_.cascadeEvents_('layout', e); - - this.area_ = area; -}; - -DygraphLayout.prototype.setAnnotations = function (ann) { - // The Dygraph object's annotations aren't parsed. We parse them here and - // save a copy. If there is no parser, then the user must be using raw format. - this.annotations = []; - var parse = this.dygraph_.getOption('xValueParser') || function (x) { - return x; - }; - for (var i = 0; i < ann.length; i++) { - var a = {}; - if (!ann[i].xval && ann[i].x === undefined) { - console.error("Annotations must have an 'x' property"); - return; - } - if (ann[i].icon && !(ann[i].hasOwnProperty('width') && ann[i].hasOwnProperty('height'))) { - console.error("Must set width and height when setting " + "annotation.icon property"); - return; - } - utils.update(a, ann[i]); - if (!a.xval) a.xval = parse(a.x); - this.annotations.push(a); - } -}; - -DygraphLayout.prototype.setXTicks = function (xTicks) { - this.xTicks_ = xTicks; -}; - -// TODO(danvk): add this to the Dygraph object's API or move it into Layout. -DygraphLayout.prototype.setYAxes = function (yAxes) { - this.yAxes_ = yAxes; -}; - -DygraphLayout.prototype.evaluate = function () { - this._xAxis = {}; - this._evaluateLimits(); - this._evaluateLineCharts(); - this._evaluateLineTicks(); - this._evaluateAnnotations(); -}; - -DygraphLayout.prototype._evaluateLimits = function () { - var xlimits = this.dygraph_.xAxisRange(); - this._xAxis.minval = xlimits[0]; - this._xAxis.maxval = xlimits[1]; - var xrange = xlimits[1] - xlimits[0]; - this._xAxis.scale = xrange !== 0 ? 1 / xrange : 1.0; - - if (this.dygraph_.getOptionForAxis("logscale", 'x')) { - this._xAxis.xlogrange = utils.log10(this._xAxis.maxval) - utils.log10(this._xAxis.minval); - this._xAxis.xlogscale = this._xAxis.xlogrange !== 0 ? 1.0 / this._xAxis.xlogrange : 1.0; - } - for (var i = 0; i < this.yAxes_.length; i++) { - var axis = this.yAxes_[i]; - axis.minyval = axis.computedValueRange[0]; - axis.maxyval = axis.computedValueRange[1]; - axis.yrange = axis.maxyval - axis.minyval; - axis.yscale = axis.yrange !== 0 ? 1.0 / axis.yrange : 1.0; - - if (this.dygraph_.getOption("logscale")) { - axis.ylogrange = utils.log10(axis.maxyval) - utils.log10(axis.minyval); - axis.ylogscale = axis.ylogrange !== 0 ? 1.0 / axis.ylogrange : 1.0; - if (!isFinite(axis.ylogrange) || isNaN(axis.ylogrange)) { - console.error('axis ' + i + ' of graph at ' + axis.g + ' can\'t be displayed in log scale for range [' + axis.minyval + ' - ' + axis.maxyval + ']'); - } - } - } -}; - -DygraphLayout.calcXNormal_ = function (value, xAxis, logscale) { - if (logscale) { - return (utils.log10(value) - utils.log10(xAxis.minval)) * xAxis.xlogscale; - } else { - return (value - xAxis.minval) * xAxis.scale; - } -}; - -/** - * @param {DygraphAxisType} axis - * @param {number} value - * @param {boolean} logscale - * @return {number} - */ -DygraphLayout.calcYNormal_ = function (axis, value, logscale) { - if (logscale) { - var x = 1.0 - (utils.log10(value) - utils.log10(axis.minyval)) * axis.ylogscale; - return isFinite(x) ? x : NaN; // shim for v8 issue; see pull request 276 - } else { - return 1.0 - (value - axis.minyval) * axis.yscale; - } -}; - -DygraphLayout.prototype._evaluateLineCharts = function () { - var isStacked = this.dygraph_.getOption("stackedGraph"); - var isLogscaleForX = this.dygraph_.getOptionForAxis("logscale", 'x'); - - for (var setIdx = 0; setIdx < this.points.length; setIdx++) { - var points = this.points[setIdx]; - var setName = this.setNames[setIdx]; - var connectSeparated = this.dygraph_.getOption('connectSeparatedPoints', setName); - var axis = this.dygraph_.axisPropertiesForSeries(setName); - // TODO (konigsberg): use optionsForAxis instead. - var logscale = this.dygraph_.attributes_.getForSeries("logscale", setName); - - for (var j = 0; j < points.length; j++) { - var point = points[j]; - - // Range from 0-1 where 0 represents left and 1 represents right. - point.x = DygraphLayout.calcXNormal_(point.xval, this._xAxis, isLogscaleForX); - // Range from 0-1 where 0 represents top and 1 represents bottom - var yval = point.yval; - if (isStacked) { - point.y_stacked = DygraphLayout.calcYNormal_(axis, point.yval_stacked, logscale); - if (yval !== null && !isNaN(yval)) { - yval = point.yval_stacked; - } - } - if (yval === null) { - yval = NaN; - if (!connectSeparated) { - point.yval = NaN; - } - } - point.y = DygraphLayout.calcYNormal_(axis, yval, logscale); - } - - this.dygraph_.dataHandler_.onLineEvaluated(points, axis, logscale); - } -}; - -DygraphLayout.prototype._evaluateLineTicks = function () { - var i, tick, label, pos, v, has_tick; - this.xticks = []; - for (i = 0; i < this.xTicks_.length; i++) { - tick = this.xTicks_[i]; - label = tick.label; - has_tick = !('label_v' in tick); - v = has_tick ? tick.v : tick.label_v; - pos = this.dygraph_.toPercentXCoord(v); - if (pos >= 0.0 && pos < 1.0) { - this.xticks.push({ pos: pos, label: label, has_tick: has_tick }); - } - } - - this.yticks = []; - for (i = 0; i < this.yAxes_.length; i++) { - var axis = this.yAxes_[i]; - for (var j = 0; j < axis.ticks.length; j++) { - tick = axis.ticks[j]; - label = tick.label; - has_tick = !('label_v' in tick); - v = has_tick ? tick.v : tick.label_v; - pos = this.dygraph_.toPercentYCoord(v, i); - if (pos > 0.0 && pos <= 1.0) { - this.yticks.push({ axis: i, pos: pos, label: label, has_tick: has_tick }); - } - } - } -}; - -DygraphLayout.prototype._evaluateAnnotations = function () { - // Add the annotations to the point to which they belong. - // Make a map from (setName, xval) to annotation for quick lookups. - var i; - var annotations = {}; - for (i = 0; i < this.annotations.length; i++) { - var a = this.annotations[i]; - annotations[a.xval + "," + a.series] = a; - } - - this.annotated_points = []; - - // Exit the function early if there are no annotations. - if (!this.annotations || !this.annotations.length) { - return; - } - - // TODO(antrob): loop through annotations not points. - for (var setIdx = 0; setIdx < this.points.length; setIdx++) { - var points = this.points[setIdx]; - for (i = 0; i < points.length; i++) { - var p = points[i]; - var k = p.xval + "," + p.name; - if (k in annotations) { - p.annotation = annotations[k]; - this.annotated_points.push(p); - } - } - } -}; - -/** - * Convenience function to remove all the data sets from a graph - */ -DygraphLayout.prototype.removeAllDatasets = function () { - delete this.points; - delete this.setNames; - delete this.setPointsLengths; - delete this.setPointsOffsets; - this.points = []; - this.setNames = []; - this.setPointsLengths = []; - this.setPointsOffsets = []; -}; - -exports['default'] = DygraphLayout; -module.exports = exports['default']; - -},{"./dygraph-utils":17}],14:[function(require,module,exports){ -(function (process){ -/** - * @license - * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -"use strict"; - -Object.defineProperty(exports, '__esModule', { - value: true -}); -var OPTIONS_REFERENCE = null; - -// For "production" code, this gets removed by uglifyjs. -if (typeof process !== 'undefined') { - if ("development" != 'production') { - - // NOTE: in addition to parsing as JS, this snippet is expected to be valid - // JSON. This assumption cannot be checked in JS, but it will be checked when - // documentation is generated by the generate-documentation.py script. For the - // most part, this just means that you should always use double quotes. - OPTIONS_REFERENCE = // - { - "xValueParser": { - "default": "parseFloat() or Date.parse()*", - "labels": ["CSV parsing"], - "type": "function(str) -> number", - "description": "A function which parses x-values (i.e. the dependent series). Must return a number, even when the values are dates. In this case, millis since epoch are used. This is used primarily for parsing CSV data. *=Dygraphs is slightly more accepting in the dates which it will parse. See code for details." - }, - "stackedGraph": { - "default": "false", - "labels": ["Data Line display"], - "type": "boolean", - "description": "If set, stack series on top of one another rather than drawing them independently. The first series specified in the input data will wind up on top of the chart and the last will be on bottom. NaN values are drawn as white areas without a line on top, see stackedGraphNaNFill for details." - }, - "stackedGraphNaNFill": { - "default": "all", - "labels": ["Data Line display"], - "type": "string", - "description": "Controls handling of NaN values inside a stacked graph. NaN values are interpolated/extended for stacking purposes, but the actual point value remains NaN in the legend display. Valid option values are \"all\" (interpolate internally, repeat leftmost and rightmost value as needed), \"inside\" (interpolate internally only, use zero outside leftmost and rightmost value), and \"none\" (treat NaN as zero everywhere)." - }, - "pointSize": { - "default": "1", - "labels": ["Data Line display"], - "type": "integer", - "description": "The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is \"isolated\", i.e. there is a missing point on either side of it. This also controls the size of those dots." - }, - "drawPoints": { - "default": "false", - "labels": ["Data Line display"], - "type": "boolean", - "description": "Draw a small dot at each point, in addition to a line going through the point. This makes the individual data points easier to see, but can increase visual clutter in the chart. The small dot can be replaced with a custom rendering by supplying a drawPointCallback." - }, - "drawGapEdgePoints": { - "default": "false", - "labels": ["Data Line display"], - "type": "boolean", - "description": "Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities." - }, - "drawPointCallback": { - "default": "null", - "labels": ["Data Line display"], - "type": "function(g, seriesName, canvasContext, cx, cy, color, pointSize)", - "parameters": [["g", "the reference graph"], ["seriesName", "the name of the series"], ["canvasContext", "the canvas to draw on"], ["cx", "center x coordinate"], ["cy", "center y coordinate"], ["color", "series color"], ["pointSize", "the radius of the image."], ["idx", "the row-index of the point in the data."]], - "description": "Draw a custom item when drawPoints is enabled. Default is a small dot matching the series color. This method should constrain drawing to within pointSize pixels from (cx, cy). Also see drawHighlightPointCallback" - }, - "height": { - "default": "320", - "labels": ["Overall display"], - "type": "integer", - "description": "Height, in pixels, of the chart. If the container div has been explicitly sized, this will be ignored." - }, - "zoomCallback": { - "default": "null", - "labels": ["Callbacks"], - "type": "function(minDate, maxDate, yRanges)", - "parameters": [["minDate", "milliseconds since epoch"], ["maxDate", "milliseconds since epoch."], ["yRanges", "is an array of [bottom, top] pairs, one for each y-axis."]], - "description": "A function to call when the zoom window is changed (either by zooming in or out). When animatedZooms is set, zoomCallback is called once at the end of the transition (it will not be called for intermediate frames)." - }, - "pointClickCallback": { - "snippet": "function(e, point){
  alert(point);
}", - "default": "null", - "labels": ["Callbacks", "Interactive Elements"], - "type": "function(e, point)", - "parameters": [["e", "the event object for the click"], ["point", "the point that was clicked See Point properties for details"]], - "description": "A function to call when a data point is clicked. and the point that was clicked." - }, - "color": { - "default": "(see description)", - "labels": ["Data Series Colors"], - "type": "string", - "example": "red", - "description": "A per-series color definition. Used in conjunction with, and overrides, the colors option." - }, - "colors": { - "default": "(see description)", - "labels": ["Data Series Colors"], - "type": "array", - "example": "['red', '#00FF00']", - "description": "List of colors for the data series. These can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\", etc. If not specified, equally-spaced points around a color wheel are used. Overridden by the 'color' option." - }, - "connectSeparatedPoints": { - "default": "false", - "labels": ["Data Line display"], - "type": "boolean", - "description": "Usually, when Dygraphs encounters a missing value in a data series, it interprets this as a gap and draws it as such. If, instead, the missing values represents an x-value for which only a different series has data, then you'll want to connect the dots by setting this to true. To explicitly include a gap with this option set, use a value of NaN." - }, - "highlightCallback": { - "default": "null", - "labels": ["Callbacks"], - "type": "function(event, x, points, row, seriesName)", - "description": "When set, this callback gets called every time a new point is highlighted.", - "parameters": [["event", "the JavaScript mousemove event"], ["x", "the x-coordinate of the highlighted points"], ["points", "an array of highlighted points: [ {name: 'series', yval: y-value}, … ]"], ["row", "integer index of the highlighted row in the data table, starting from 0"], ["seriesName", "name of the highlighted series, only present if highlightSeriesOpts is set."]] - }, - "drawHighlightPointCallback": { - "default": "null", - "labels": ["Data Line display"], - "type": "function(g, seriesName, canvasContext, cx, cy, color, pointSize)", - "parameters": [["g", "the reference graph"], ["seriesName", "the name of the series"], ["canvasContext", "the canvas to draw on"], ["cx", "center x coordinate"], ["cy", "center y coordinate"], ["color", "series color"], ["pointSize", "the radius of the image."], ["idx", "the row-index of the point in the data."]], - "description": "Draw a custom item when a point is highlighted. Default is a small dot matching the series color. This method should constrain drawing to within pointSize pixels from (cx, cy) Also see drawPointCallback" - }, - "highlightSeriesOpts": { - "default": "null", - "labels": ["Interactive Elements"], - "type": "Object", - "description": "When set, the options from this object are applied to the timeseries closest to the mouse pointer for interactive highlighting. See also 'highlightCallback'. Example: highlightSeriesOpts: { strokeWidth: 3 }." - }, - "highlightSeriesBackgroundAlpha": { - "default": "0.5", - "labels": ["Interactive Elements"], - "type": "float", - "description": "Fade the background while highlighting series. 1=fully visible background (disable fading), 0=hiddden background (show highlighted series only)." - }, - "highlightSeriesBackgroundColor": { - "default": "rgb(255, 255, 255)", - "labels": ["Interactive Elements"], - "type": "string", - "description": "Sets the background color used to fade out the series in conjunction with 'highlightSeriesBackgroundAlpha'." - }, - "includeZero": { - "default": "false", - "labels": ["Axis display"], - "type": "boolean", - "description": "Usually, dygraphs will use the range of the data plus some padding to set the range of the y-axis. If this option is set, the y-axis will always include zero, typically as the lowest value. This can be used to avoid exaggerating the variance in the data" - }, - "rollPeriod": { - "default": "1", - "labels": ["Error Bars", "Rolling Averages"], - "type": "integer >= 1", - "description": "Number of days over which to average data. Discussed extensively above." - }, - "unhighlightCallback": { - "default": "null", - "labels": ["Callbacks"], - "type": "function(event)", - "parameters": [["event", "the mouse event"]], - "description": "When set, this callback gets called every time the user stops highlighting any point by mousing out of the graph." - }, - "axisTickSize": { - "default": "3.0", - "labels": ["Axis display"], - "type": "number", - "description": "The size of the line to display next to each tick mark on x- or y-axes." - }, - "labelsSeparateLines": { - "default": "false", - "labels": ["Legend"], - "type": "boolean", - "description": "Put <br/> between lines in the label string. Often used in conjunction with labelsDiv." - }, - "valueFormatter": { - "default": "Depends on the type of your data.", - "labels": ["Legend", "Value display/formatting"], - "type": "function(num or millis, opts, seriesName, dygraph, row, col)", - "description": "Function to provide a custom display format for the values displayed on mouseover. This does not affect the values that appear on tick marks next to the axes. To format those, see axisLabelFormatter. This is usually set on a per-axis basis. .", - "parameters": [["num_or_millis", "The value to be formatted. This is always a number. For date axes, it's millis since epoch. You can call new Date(millis) to get a Date object."], ["opts", "This is a function you can call to access various options (e.g. opts('labelsKMB')). It returns per-axis values for the option when available."], ["seriesName", "The name of the series from which the point came, e.g. 'X', 'Y', 'A', etc."], ["dygraph", "The dygraph object for which the formatting is being done"], ["row", "The row of the data from which this point comes. g.getValue(row, 0) will return the x-value for this point."], ["col", "The column of the data from which this point comes. g.getValue(row, col) will return the original y-value for this point. This can be used to get the full confidence interval for the point, or access un-rolled values for the point."]] - }, - "annotationMouseOverHandler": { - "default": "null", - "labels": ["Annotations"], - "type": "function(annotation, point, dygraph, event)", - "description": "If provided, this function is called whenever the user mouses over an annotation." - }, - "annotationMouseOutHandler": { - "default": "null", - "labels": ["Annotations"], - "type": "function(annotation, point, dygraph, event)", - "parameters": [["annotation", "the annotation left"], ["point", "the point associated with the annotation"], ["dygraph", "the reference graph"], ["event", "the mouse event"]], - "description": "If provided, this function is called whenever the user mouses out of an annotation." - }, - "annotationClickHandler": { - "default": "null", - "labels": ["Annotations"], - "type": "function(annotation, point, dygraph, event)", - "parameters": [["annotation", "the annotation left"], ["point", "the point associated with the annotation"], ["dygraph", "the reference graph"], ["event", "the mouse event"]], - "description": "If provided, this function is called whenever the user clicks on an annotation." - }, - "annotationDblClickHandler": { - "default": "null", - "labels": ["Annotations"], - "type": "function(annotation, point, dygraph, event)", - "parameters": [["annotation", "the annotation left"], ["point", "the point associated with the annotation"], ["dygraph", "the reference graph"], ["event", "the mouse event"]], - "description": "If provided, this function is called whenever the user double-clicks on an annotation." - }, - "drawCallback": { - "default": "null", - "labels": ["Callbacks"], - "type": "function(dygraph, is_initial)", - "parameters": [["dygraph", "The graph being drawn"], ["is_initial", "True if this is the initial draw, false for subsequent draws."]], - "description": "When set, this callback gets called every time the dygraph is drawn. This includes the initial draw, after zooming and repeatedly while panning." - }, - "labelsKMG2": { - "default": "false", - "labels": ["Value display/formatting"], - "type": "boolean", - "description": "Show k/M/G for kilo/Mega/Giga on y-axis. This is different than labelsKMB in that it uses base 2, not 10." - }, - "delimiter": { - "default": ",", - "labels": ["CSV parsing"], - "type": "string", - "description": "The delimiter to look for when separating fields of a CSV file. Setting this to a tab is not usually necessary, since tab-delimited data is auto-detected." - }, - "axisLabelFontSize": { - "default": "14", - "labels": ["Axis display"], - "type": "integer", - "description": "Size of the font (in pixels) to use in the axis labels, both x- and y-axis." - }, - "underlayCallback": { - "default": "null", - "labels": ["Callbacks"], - "type": "function(context, area, dygraph)", - "parameters": [["context", "the canvas drawing context on which to draw"], ["area", "An object with {x,y,w,h} properties describing the drawing area."], ["dygraph", "the reference graph"]], - "description": "When set, this callback gets called before the chart is drawn. It details on how to use this." - }, - "width": { - "default": "480", - "labels": ["Overall display"], - "type": "integer", - "description": "Width, in pixels, of the chart. If the container div has been explicitly sized, this will be ignored." - }, - "pixelRatio": { - "default": "(devicePixelRatio / context.backingStoreRatio)", - "labels": ["Overall display"], - "type": "float", - "description": "Overrides the pixel ratio scaling factor for the canvas's 2d context. Ordinarily, this is set to the devicePixelRatio / (context.backingStoreRatio || 1), so on mobile devices, where the devicePixelRatio can be somewhere around 3, performance can be improved by overriding this value to something less precise, like 1, at the expense of resolution." - }, - "interactionModel": { - "default": "...", - "labels": ["Interactive Elements"], - "type": "Object", - "description": "TODO(konigsberg): document this" - }, - "ticker": { - "default": "Dygraph.dateTicker or Dygraph.numericTicks", - "labels": ["Axis display"], - "type": "function(min, max, pixels, opts, dygraph, vals) -> [{v: ..., label: ...}, ...]", - "parameters": [["min", ""], ["max", ""], ["pixels", ""], ["opts", ""], ["dygraph", "the reference graph"], ["vals", ""]], - "description": "This lets you specify an arbitrary function to generate tick marks on an axis. The tick marks are an array of (value, label) pairs. The built-in functions go to great lengths to choose good tick marks so, if you set this option, you'll most likely want to call one of them and modify the result. See dygraph-tickers.js for an extensive discussion. This is set on a per-axis basis." - }, - "xAxisHeight": { - "default": "(null)", - "labels": ["Axis display"], - "type": "integer", - "description": "Height, in pixels, of the x-axis. If not set explicitly, this is computed based on axisLabelFontSize and axisTickSize." - }, - "showLabelsOnHighlight": { - "default": "true", - "labels": ["Interactive Elements", "Legend"], - "type": "boolean", - "description": "Whether to show the legend upon mouseover." - }, - "axis": { - "default": "(none)", - "labels": ["Axis display"], - "type": "string", - "description": "Set to either 'y1' or 'y2' to assign a series to a y-axis (primary or secondary). Must be set per-series." - }, - "pixelsPerLabel": { - "default": "70 (x-axis) or 30 (y-axes)", - "labels": ["Axis display", "Grid"], - "type": "integer", - "description": "Number of pixels to require between each x- and y-label. Larger values will yield a sparser axis with fewer ticks. This is set on a per-axis basis." - }, - "labelsDiv": { - "default": "null", - "labels": ["Legend"], - "type": "DOM element or string", - "example": "document.getElementById('foo')or'foo'", - "description": "Show data labels in an external div, rather than on the graph. This value can either be a div element or a div id." - }, - "fractions": { - "default": "false", - "labels": ["CSV parsing", "Error Bars"], - "type": "boolean", - "description": "When set, attempt to parse each cell in the CSV file as \"a/b\", where a and b are integers. The ratio will be plotted. This allows computation of Wilson confidence intervals (see below)." - }, - "logscale": { - "default": "false", - "labels": ["Axis display"], - "type": "boolean", - "description": "When set for the y-axis or x-axis, the graph shows that axis in log scale. Any values less than or equal to zero are not displayed. Showing log scale with ranges that go below zero will result in an unviewable graph.\n\n Not compatible with showZero. connectSeparatedPoints is ignored. This is ignored for date-based x-axes." - }, - "strokeWidth": { - "default": "1.0", - "labels": ["Data Line display"], - "type": "float", - "example": "0.5, 2.0", - "description": "The width of the lines connecting data points. This can be used to increase the contrast or some graphs." - }, - "strokePattern": { - "default": "null", - "labels": ["Data Line display"], - "type": "array", - "example": "[10, 2, 5, 2]", - "description": "A custom pattern array where the even index is a draw and odd is a space in pixels. If null then it draws a solid line. The array should have a even length as any odd lengthed array could be expressed as a smaller even length array. This is used to create dashed lines." - }, - "strokeBorderWidth": { - "default": "null", - "labels": ["Data Line display"], - "type": "float", - "example": "1.0", - "description": "Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines." - }, - "strokeBorderColor": { - "default": "white", - "labels": ["Data Line display"], - "type": "string", - "example": "red, #ccffdd", - "description": "Color for the line border used if strokeBorderWidth is set." - }, - "wilsonInterval": { - "default": "true", - "labels": ["Error Bars"], - "type": "boolean", - "description": "Use in conjunction with the \"fractions\" option. Instead of plotting +/- N standard deviations, dygraphs will compute a Wilson confidence interval and plot that. This has more reasonable behavior for ratios close to 0 or 1." - }, - "fillGraph": { - "default": "false", - "labels": ["Data Line display"], - "type": "boolean", - "description": "Should the area underneath the graph be filled? This option is not compatible with error bars. This may be set on a per-series basis." - }, - "highlightCircleSize": { - "default": "3", - "labels": ["Interactive Elements"], - "type": "integer", - "description": "The size in pixels of the dot drawn over highlighted points." - }, - "gridLineColor": { - "default": "rgb(128,128,128)", - "labels": ["Grid"], - "type": "red, blue", - "description": "The color of the gridlines. This may be set on a per-axis basis to define each axis' grid separately." - }, - "gridLinePattern": { - "default": "null", - "labels": ["Grid"], - "type": "array", - "example": "[10, 2, 5, 2]", - "description": "A custom pattern array where the even index is a draw and odd is a space in pixels. If null then it draws a solid line. The array should have a even length as any odd lengthed array could be expressed as a smaller even length array. This is used to create dashed gridlines." - }, - "visibility": { - "default": "[true, true, ...]", - "labels": ["Data Line display"], - "type": "Array of booleans", - "description": "Which series should initially be visible? Once the Dygraph has been constructed, you can access and modify the visibility of each series using the visibility and setVisibility methods." - }, - "valueRange": { - "default": "Full range of the input is shown", - "labels": ["Axis display"], - "type": "Array of two numbers", - "example": "[10, 110]", - "description": "Explicitly set the vertical range of the graph to [low, high]. This may be set on a per-axis basis to define each y-axis separately. If either limit is unspecified, it will be calculated automatically (e.g. [null, 30] to automatically calculate just the lower bound)" - }, - "colorSaturation": { - "default": "1.0", - "labels": ["Data Series Colors"], - "type": "float (0.0 - 1.0)", - "description": "If colors is not specified, saturation of the automatically-generated data series colors." - }, - "hideOverlayOnMouseOut": { - "default": "true", - "labels": ["Interactive Elements", "Legend"], - "type": "boolean", - "description": "Whether to hide the legend when the mouse leaves the chart area." - }, - "legend": { - "default": "onmouseover", - "labels": ["Legend"], - "type": "string", - "description": "When to display the legend. By default, it only appears when a user mouses over the chart. Set it to \"always\" to always display a legend of some sort. When set to \"follow\", legend follows highlighted points." - }, - "legendFormatter": { - "default": "null", - "labels": ["Legend"], - "type": "function(data): string", - "params": [["data", "An object containing information about the selection (or lack of a selection). This includes formatted values and series information. See here for sample values."]], - "description": "Set this to supply a custom formatter for the legend. See this comment and the legendFormatter demo for usage." - }, - "labelsShowZeroValues": { - "default": "true", - "labels": ["Legend"], - "type": "boolean", - "description": "Show zero value labels in the labelsDiv." - }, - "stepPlot": { - "default": "false", - "labels": ["Data Line display"], - "type": "boolean", - "description": "When set, display the graph as a step plot instead of a line plot. This option may either be set for the whole graph or for single series." - }, - "labelsUTC": { - "default": "false", - "labels": ["Value display/formatting", "Axis display"], - "type": "boolean", - "description": "Show date/time labels according to UTC (instead of local time)." - }, - "labelsKMB": { - "default": "false", - "labels": ["Value display/formatting"], - "type": "boolean", - "description": "Show K/M/B for thousands/millions/billions on y-axis." - }, - "rightGap": { - "default": "5", - "labels": ["Overall display"], - "type": "integer", - "description": "Number of pixels to leave blank at the right edge of the Dygraph. This makes it easier to highlight the right-most data point." - }, - "drawAxesAtZero": { - "default": "false", - "labels": ["Axis display"], - "type": "boolean", - "description": "When set, draw the X axis at the Y=0 position and the Y axis at the X=0 position if those positions are inside the graph's visible area. Otherwise, draw the axes at the bottom or left graph edge as usual." - }, - "xRangePad": { - "default": "0", - "labels": ["Axis display"], - "type": "float", - "description": "Add the specified amount of extra space (in pixels) around the X-axis value range to ensure points at the edges remain visible." - }, - "yRangePad": { - "default": "null", - "labels": ["Axis display"], - "type": "float", - "description": "If set, add the specified amount of extra space (in pixels) around the Y-axis value range to ensure points at the edges remain visible. If unset, use the traditional Y padding algorithm." - }, - "axisLabelFormatter": { - "default": "Depends on the data type", - "labels": ["Axis display"], - "type": "function(number or Date, granularity, opts, dygraph)", - "parameters": [["number or date", "Either a number (for a numeric axis) or a Date object (for a date axis)"], ["granularity", "specifies how fine-grained the axis is. For date axes, this is a reference to the time granularity enumeration, defined in dygraph-tickers.js, e.g. Dygraph.WEEKLY."], ["opts", "a function which provides access to various options on the dygraph, e.g. opts('labelsKMB')."], ["dygraph", "the referenced graph"]], - "description": "Function to call to format the tick values that appear along an axis. This is usually set on a per-axis basis." - }, - "clickCallback": { - "snippet": "function(e, date_millis){
  alert(new Date(date_millis));
}", - "default": "null", - "labels": ["Callbacks"], - "type": "function(e, x, points)", - "parameters": [["e", "The event object for the click"], ["x", "The x value that was clicked (for dates, this is milliseconds since epoch)"], ["points", "The closest points along that date. See Point properties for details."]], - "description": "A function to call when the canvas is clicked." - }, - "labels": { - "default": "[\"X\", \"Y1\", \"Y2\", ...]*", - "labels": ["Legend"], - "type": "array", - "description": "A name for each data series, including the independent (X) series. For CSV files and DataTable objections, this is determined by context. For raw data, this must be specified. If it is not, default values are supplied and a warning is logged." - }, - "dateWindow": { - "default": "Full range of the input is shown", - "labels": ["Axis display"], - "type": "Array of two numbers", - "example": "[
  Date.parse('2006-01-01'),
  (new Date()).valueOf()
]", - "description": "Initially zoom in on a section of the graph. Is of the form [earliest, latest], where earliest/latest are milliseconds since epoch. If the data for the x-axis is numeric, the values in dateWindow must also be numbers." - }, - "showRoller": { - "default": "false", - "labels": ["Interactive Elements", "Rolling Averages"], - "type": "boolean", - "description": "If the rolling average period text box should be shown." - }, - "sigma": { - "default": "2.0", - "labels": ["Error Bars"], - "type": "float", - "description": "When errorBars is set, shade this many standard deviations above/below each point." - }, - "customBars": { - "default": "false", - "labels": ["CSV parsing", "Error Bars"], - "type": "boolean", - "description": "When set, parse each CSV cell as \"low;middle;high\". Error bars will be drawn for each point between low and high, with the series itself going through middle." - }, - "colorValue": { - "default": "1.0", - "labels": ["Data Series Colors"], - "type": "float (0.0 - 1.0)", - "description": "If colors is not specified, value of the data series colors, as in hue/saturation/value. (0.0-1.0, default 0.5)" - }, - "errorBars": { - "default": "false", - "labels": ["CSV parsing", "Error Bars"], - "type": "boolean", - "description": "Does the data contain standard deviations? Setting this to true alters the input format (see above)." - }, - "displayAnnotations": { - "default": "false", - "labels": ["Annotations"], - "type": "boolean", - "description": "Only applies when Dygraphs is used as a GViz chart. Causes string columns following a data series to be interpreted as annotations on points in that series. This is the same format used by Google's AnnotatedTimeLine chart." - }, - "panEdgeFraction": { - "default": "null", - "labels": ["Axis display", "Interactive Elements"], - "type": "float", - "description": "A value representing the farthest a graph may be panned, in percent of the display. For example, a value of 0.1 means that the graph can only be panned 10% passed the edges of the displayed values. null means no bounds." - }, - "title": { - "labels": ["Chart labels"], - "type": "string", - "default": "null", - "description": "Text to display above the chart. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-title' classes." - }, - "titleHeight": { - "default": "18", - "labels": ["Chart labels"], - "type": "integer", - "description": "Height of the chart title, in pixels. This also controls the default font size of the title. If you style the title on your own, this controls how much space is set aside above the chart for the title's div." - }, - "xlabel": { - "labels": ["Chart labels"], - "type": "string", - "default": "null", - "description": "Text to display below the chart's x-axis. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-xlabel' classes." - }, - "xLabelHeight": { - "labels": ["Chart labels"], - "type": "integer", - "default": "18", - "description": "Height of the x-axis label, in pixels. This also controls the default font size of the x-axis label. If you style the label on your own, this controls how much space is set aside below the chart for the x-axis label's div." - }, - "ylabel": { - "labels": ["Chart labels"], - "type": "string", - "default": "null", - "description": "Text to display to the left of the chart's y-axis. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-ylabel' classes. The text will be rotated 90 degrees by default, so CSS rules may behave in unintuitive ways. No additional space is set aside for a y-axis label. If you need more space, increase the width of the y-axis tick labels using the yAxisLabelWidth option. If you need a wider div for the y-axis label, either style it that way with CSS (but remember that it's rotated, so width is controlled by the 'height' property) or set the yLabelWidth option." - }, - "y2label": { - "labels": ["Chart labels"], - "type": "string", - "default": "null", - "description": "Text to display to the right of the chart's secondary y-axis. This label is only displayed if a secondary y-axis is present. See this test for an example of how to do this. The comments for the 'ylabel' option generally apply here as well. This label gets a 'dygraph-y2label' instead of a 'dygraph-ylabel' class." - }, - "yLabelWidth": { - "labels": ["Chart labels"], - "type": "integer", - "default": "18", - "description": "Width of the div which contains the y-axis label. Since the y-axis label appears rotated 90 degrees, this actually affects the height of its div." - }, - "drawGrid": { - "default": "true for x and y, false for y2", - "labels": ["Grid"], - "type": "boolean", - "description": "Whether to display gridlines in the chart. This may be set on a per-axis basis to define the visibility of each axis' grid separately." - }, - "independentTicks": { - "default": "true for y, false for y2", - "labels": ["Axis display", "Grid"], - "type": "boolean", - "description": "Only valid for y and y2, has no effect on x: This option defines whether the y axes should align their ticks or if they should be independent. Possible combinations: 1.) y=true, y2=false (default): y is the primary axis and the y2 ticks are aligned to the the ones of y. (only 1 grid) 2.) y=false, y2=true: y2 is the primary axis and the y ticks are aligned to the the ones of y2. (only 1 grid) 3.) y=true, y2=true: Both axis are independent and have their own ticks. (2 grids) 4.) y=false, y2=false: Invalid configuration causes an error." - }, - "drawAxis": { - "default": "true for x and y, false for y2", - "labels": ["Axis display"], - "type": "boolean", - "description": "Whether to draw the specified axis. This may be set on a per-axis basis to define the visibility of each axis separately. Setting this to false also prevents axis ticks from being drawn and reclaims the space for the chart grid/lines." - }, - "gridLineWidth": { - "default": "0.3", - "labels": ["Grid"], - "type": "float", - "description": "Thickness (in pixels) of the gridlines drawn under the chart. The vertical/horizontal gridlines can be turned off entirely by using the drawGrid option. This may be set on a per-axis basis to define each axis' grid separately." - }, - "axisLineWidth": { - "default": "0.3", - "labels": ["Axis display"], - "type": "float", - "description": "Thickness (in pixels) of the x- and y-axis lines." - }, - "axisLineColor": { - "default": "black", - "labels": ["Axis display"], - "type": "string", - "description": "Color of the x- and y-axis lines. Accepts any value which the HTML canvas strokeStyle attribute understands, e.g. 'black' or 'rgb(0, 100, 255)'." - }, - "fillAlpha": { - "default": "0.15", - "labels": ["Error Bars", "Data Series Colors"], - "type": "float (0.0 - 1.0)", - "description": "Error bars (or custom bars) for each series are drawn in the same color as the series, but with partial transparency. This sets the transparency. A value of 0.0 means that the error bars will not be drawn, whereas a value of 1.0 means that the error bars will be as dark as the line for the series itself. This can be used to produce chart lines whose thickness varies at each point." - }, - "axisLabelWidth": { - "default": "50 (y-axis), 60 (x-axis)", - "labels": ["Axis display", "Chart labels"], - "type": "integer", - "description": "Width (in pixels) of the containing divs for x- and y-axis labels. For the y-axis, this also controls the width of the y-axis. Note that for the x-axis, this is independent from pixelsPerLabel, which controls the spacing between labels." - }, - "sigFigs": { - "default": "null", - "labels": ["Value display/formatting"], - "type": "integer", - "description": "By default, dygraphs displays numbers with a fixed number of digits after the decimal point. If you'd prefer to have a fixed number of significant figures, set this option to that number of sig figs. A value of 2, for instance, would cause 1 to be display as 1.0 and 1234 to be displayed as 1.23e+3." - }, - "digitsAfterDecimal": { - "default": "2", - "labels": ["Value display/formatting"], - "type": "integer", - "description": "Unless it's run in scientific mode (see the sigFigs option), dygraphs displays numbers with digitsAfterDecimal digits after the decimal point. Trailing zeros are not displayed, so with a value of 2 you'll get '0', '0.1', '0.12', '123.45' but not '123.456' (it will be rounded to '123.46'). Numbers with absolute value less than 0.1^digitsAfterDecimal (i.e. those which would show up as '0.00') will be displayed in scientific notation." - }, - "maxNumberWidth": { - "default": "6", - "labels": ["Value display/formatting"], - "type": "integer", - "description": "When displaying numbers in normal (not scientific) mode, large numbers will be displayed with many trailing zeros (e.g. 100000000 instead of 1e9). This can lead to unwieldy y-axis labels. If there are more than maxNumberWidth digits to the left of the decimal in a number, dygraphs will switch to scientific notation, even when not operating in scientific mode. If you'd like to see all those digits, set this to something large, like 20 or 30." - }, - "file": { - "default": "(set when constructed)", - "labels": ["Data"], - "type": "string (URL of CSV or CSV), GViz DataTable or 2D Array", - "description": "Sets the data being displayed in the chart. This can only be set when calling updateOptions; it cannot be set from the constructor. For a full description of valid data formats, see the Data Formats page." - }, - "timingName": { - "default": "null", - "labels": ["Debugging", "Deprecated"], - "type": "string", - "description": "Set this option to log timing information. The value of the option will be logged along with the timimg, so that you can distinguish multiple dygraphs on the same page." - }, - "showRangeSelector": { - "default": "false", - "labels": ["Range Selector"], - "type": "boolean", - "description": "Show or hide the range selector widget." - }, - "rangeSelectorHeight": { - "default": "40", - "labels": ["Range Selector"], - "type": "integer", - "description": "Height, in pixels, of the range selector widget. This option can only be specified at Dygraph creation time." - }, - "rangeSelectorPlotStrokeColor": { - "default": "#808FAB", - "labels": ["Range Selector"], - "type": "string", - "description": "The range selector mini plot stroke color. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\". You can also specify null or \"\" to turn off stroke." - }, - "rangeSelectorPlotFillColor": { - "default": "#A7B1C4", - "labels": ["Range Selector"], - "type": "string", - "description": "The range selector mini plot fill color. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\". You can also specify null or \"\" to turn off fill." - }, - "rangeSelectorPlotFillGradientColor": { - "default": "white", - "labels": ["Range Selector"], - "type": "string", - "description": "The top color for the range selector mini plot fill color gradient. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"rgba(255,100,200,42)\" or \"yellow\". You can also specify null or \"\" to disable the gradient and fill with one single color." - }, - "rangeSelectorBackgroundStrokeColor": { - "default": "gray", - "labels": ["Range Selector"], - "type": "string", - "description": "The color of the lines below and on both sides of the range selector mini plot. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\"." - }, - "rangeSelectorBackgroundLineWidth": { - "default": "1", - "labels": ["Range Selector"], - "type": "float", - "description": "The width of the lines below and on both sides of the range selector mini plot." - }, - "rangeSelectorPlotLineWidth": { - "default": "1.5", - "labels": ["Range Selector"], - "type": "float", - "description": "The width of the range selector mini plot line." - }, - "rangeSelectorForegroundStrokeColor": { - "default": "black", - "labels": ["Range Selector"], - "type": "string", - "description": "The color of the lines in the interactive layer of the range selector. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\"." - }, - "rangeSelectorForegroundLineWidth": { - "default": "1", - "labels": ["Range Selector"], - "type": "float", - "description": "The width the lines in the interactive layer of the range selector." - }, - "rangeSelectorAlpha": { - "default": "0.6", - "labels": ["Range Selector"], - "type": "float (0.0 - 1.0)", - "description": "The transparency of the veil that is drawn over the unselected portions of the range selector mini plot. A value of 0 represents full transparency and the unselected portions of the mini plot will appear as normal. A value of 1 represents full opacity and the unselected portions of the mini plot will be hidden." - }, - "showInRangeSelector": { - "default": "null", - "labels": ["Range Selector"], - "type": "boolean", - "description": "Mark this series for inclusion in the range selector. The mini plot curve will be an average of all such series. If this is not specified for any series, the default behavior is to average all the visible series. Setting it for one series will result in that series being charted alone in the range selector. Once it's set for a single series, it needs to be set for all series which should be included (regardless of visibility)." - }, - "animatedZooms": { - "default": "false", - "labels": ["Interactive Elements"], - "type": "boolean", - "description": "Set this option to animate the transition between zoom windows. Applies to programmatic and interactive zooms. Note that if you also set a drawCallback, it will be called several times on each zoom. If you set a zoomCallback, it will only be called after the animation is complete." - }, - "plotter": { - "default": "[DygraphCanvasRenderer.Plotters.fillPlotter, DygraphCanvasRenderer.Plotters.errorPlotter, DygraphCanvasRenderer.Plotters.linePlotter]", - "labels": ["Data Line display"], - "type": "array or function", - "description": "A function (or array of functions) which plot each data series on the chart. TODO(danvk): more details! May be set per-series." - }, - "axes": { - "default": "null", - "labels": ["Configuration"], - "type": "Object", - "description": "Defines per-axis options. Valid keys are 'x', 'y' and 'y2'. Only some options may be set on a per-axis basis. If an option may be set in this way, it will be noted on this page. See also documentation on per-series and per-axis options." - }, - "series": { - "default": "null", - "labels": ["Series"], - "type": "Object", - "description": "Defines per-series options. Its keys match the y-axis label names, and the values are dictionaries themselves that contain options specific to that series." - }, - "plugins": { - "default": "[]", - "labels": ["Configuration"], - "type": "Array", - "description": "Defines per-graph plugins. Useful for per-graph customization" - }, - "dataHandler": { - "default": "(depends on data)", - "labels": ["Data"], - "type": "Dygraph.DataHandler", - "description": "Custom DataHandler. This is an advanced customization. See http://bit.ly/151E7Aq." - } - }; //
- // NOTE: in addition to parsing as JS, this snippet is expected to be valid - // JSON. This assumption cannot be checked in JS, but it will be checked when - // documentation is generated by the generate-documentation.py script. For the - // most part, this just means that you should always use double quotes. - - // Do a quick sanity check on the options reference. - var warn = function warn(msg) { - if (window.console) window.console.warn(msg); - }; - var flds = ['type', 'default', 'description']; - var valid_cats = ['Annotations', 'Axis display', 'Chart labels', 'CSV parsing', 'Callbacks', 'Data', 'Data Line display', 'Data Series Colors', 'Error Bars', 'Grid', 'Interactive Elements', 'Range Selector', 'Legend', 'Overall display', 'Rolling Averages', 'Series', 'Value display/formatting', 'Zooming', 'Debugging', 'Configuration', 'Deprecated']; - var i; - var cats = {}; - for (i = 0; i < valid_cats.length; i++) cats[valid_cats[i]] = true; - - for (var k in OPTIONS_REFERENCE) { - if (!OPTIONS_REFERENCE.hasOwnProperty(k)) continue; - var op = OPTIONS_REFERENCE[k]; - for (i = 0; i < flds.length; i++) { - if (!op.hasOwnProperty(flds[i])) { - warn('Option ' + k + ' missing "' + flds[i] + '" property'); - } else if (typeof op[flds[i]] != 'string') { - warn(k + '.' + flds[i] + ' must be of type string'); - } - } - var labels = op.labels; - if (typeof labels !== 'object') { - warn('Option "' + k + '" is missing a "labels": [...] option'); - } else { - for (i = 0; i < labels.length; i++) { - if (!cats.hasOwnProperty(labels[i])) { - warn('Option "' + k + '" has label "' + labels[i] + '", which is invalid.'); - } - } - } - } - } -} - -exports['default'] = OPTIONS_REFERENCE; -module.exports = exports['default']; - -}).call(this,require('_process')) - -},{"_process":1}],15:[function(require,module,exports){ -(function (process){ -/** - * @license - * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/** - * @fileoverview DygraphOptions is responsible for parsing and returning - * information about options. - */ - -// TODO: remove this jshint directive & fix the warnings. -/*jshint sub:true */ -"use strict"; - -Object.defineProperty(exports, '__esModule', { - value: true -}); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -var _dygraphUtils = require('./dygraph-utils'); - -var utils = _interopRequireWildcard(_dygraphUtils); - -var _dygraphDefaultAttrs = require('./dygraph-default-attrs'); - -var _dygraphDefaultAttrs2 = _interopRequireDefault(_dygraphDefaultAttrs); - -var _dygraphOptionsReference = require('./dygraph-options-reference'); - -var _dygraphOptionsReference2 = _interopRequireDefault(_dygraphOptionsReference); - -/* - * Interesting member variables: (REMOVING THIS LIST AS I CLOSURIZE) - * global_ - global attributes (common among all graphs, AIUI) - * user - attributes set by the user - * series_ - { seriesName -> { idx, yAxis, options }} - */ - -/** - * This parses attributes into an object that can be easily queried. - * - * It doesn't necessarily mean that all options are available, specifically - * if labels are not yet available, since those drive details of the per-series - * and per-axis options. - * - * @param {Dygraph} dygraph The chart to which these options belong. - * @constructor - */ -var DygraphOptions = function DygraphOptions(dygraph) { - /** - * The dygraph. - * @type {!Dygraph} - */ - this.dygraph_ = dygraph; - - /** - * Array of axis index to { series : [ series names ] , options : { axis-specific options. } - * @type {Array.<{series : Array., options : Object}>} @private - */ - this.yAxes_ = []; - - /** - * Contains x-axis specific options, which are stored in the options key. - * This matches the yAxes_ object structure (by being a dictionary with an - * options element) allowing for shared code. - * @type {options: Object} @private - */ - this.xAxis_ = {}; - this.series_ = {}; - - // Once these two objects are initialized, you can call get(); - this.global_ = this.dygraph_.attrs_; - this.user_ = this.dygraph_.user_attrs_ || {}; - - /** - * A list of series in columnar order. - * @type {Array.} - */ - this.labels_ = []; - - this.highlightSeries_ = this.get("highlightSeriesOpts") || {}; - this.reparseSeries(); -}; - -/** - * Not optimal, but does the trick when you're only using two axes. - * If we move to more axes, this can just become a function. - * - * @type {Object.} - * @private - */ -DygraphOptions.AXIS_STRING_MAPPINGS_ = { - 'y': 0, - 'Y': 0, - 'y1': 0, - 'Y1': 0, - 'y2': 1, - 'Y2': 1 -}; - -/** - * @param {string|number} axis - * @private - */ -DygraphOptions.axisToIndex_ = function (axis) { - if (typeof axis == "string") { - if (DygraphOptions.AXIS_STRING_MAPPINGS_.hasOwnProperty(axis)) { - return DygraphOptions.AXIS_STRING_MAPPINGS_[axis]; - } - throw "Unknown axis : " + axis; - } - if (typeof axis == "number") { - if (axis === 0 || axis === 1) { - return axis; - } - throw "Dygraphs only supports two y-axes, indexed from 0-1."; - } - if (axis) { - throw "Unknown axis : " + axis; - } - // No axis specification means axis 0. - return 0; -}; - -/** - * Reparses options that are all related to series. This typically occurs when - * options are either updated, or source data has been made available. - * - * TODO(konigsberg): The method name is kind of weak; fix. - */ -DygraphOptions.prototype.reparseSeries = function () { - var labels = this.get("labels"); - if (!labels) { - return; // -- can't do more for now, will parse after getting the labels. - } - - this.labels_ = labels.slice(1); - - this.yAxes_ = [{ series: [], options: {} }]; // Always one axis at least. - this.xAxis_ = { options: {} }; - this.series_ = {}; - - // Series are specified in the series element: - // - // { - // labels: [ "X", "foo", "bar" ], - // pointSize: 3, - // series : { - // foo : {}, // options for foo - // bar : {} // options for bar - // } - // } - // - // So, if series is found, it's expected to contain per-series data, otherwise set a - // default. - var seriesDict = this.user_.series || {}; - for (var idx = 0; idx < this.labels_.length; idx++) { - var seriesName = this.labels_[idx]; - var optionsForSeries = seriesDict[seriesName] || {}; - var yAxis = DygraphOptions.axisToIndex_(optionsForSeries["axis"]); - - this.series_[seriesName] = { - idx: idx, - yAxis: yAxis, - options: optionsForSeries }; - - if (!this.yAxes_[yAxis]) { - this.yAxes_[yAxis] = { series: [seriesName], options: {} }; - } else { - this.yAxes_[yAxis].series.push(seriesName); - } - } - - var axis_opts = this.user_["axes"] || {}; - utils.update(this.yAxes_[0].options, axis_opts["y"] || {}); - if (this.yAxes_.length > 1) { - utils.update(this.yAxes_[1].options, axis_opts["y2"] || {}); - } - utils.update(this.xAxis_.options, axis_opts["x"] || {}); - - // For "production" code, this gets removed by uglifyjs. - if (typeof process !== 'undefined') { - if ("development" != 'production') { - this.validateOptions_(); - } - } -}; - -/** - * Get a global value. - * - * @param {string} name the name of the option. - */ -DygraphOptions.prototype.get = function (name) { - var result = this.getGlobalUser_(name); - if (result !== null) { - return result; - } - return this.getGlobalDefault_(name); -}; - -DygraphOptions.prototype.getGlobalUser_ = function (name) { - if (this.user_.hasOwnProperty(name)) { - return this.user_[name]; - } - return null; -}; - -DygraphOptions.prototype.getGlobalDefault_ = function (name) { - if (this.global_.hasOwnProperty(name)) { - return this.global_[name]; - } - if (_dygraphDefaultAttrs2['default'].hasOwnProperty(name)) { - return _dygraphDefaultAttrs2['default'][name]; - } - return null; -}; - -/** - * Get a value for a specific axis. If there is no specific value for the axis, - * the global value is returned. - * - * @param {string} name the name of the option. - * @param {string|number} axis the axis to search. Can be the string representation - * ("y", "y2") or the axis number (0, 1). - */ -DygraphOptions.prototype.getForAxis = function (name, axis) { - var axisIdx; - var axisString; - - // Since axis can be a number or a string, straighten everything out here. - if (typeof axis == 'number') { - axisIdx = axis; - axisString = axisIdx === 0 ? "y" : "y2"; - } else { - if (axis == "y1") { - axis = "y"; - } // Standardize on 'y'. Is this bad? I think so. - if (axis == "y") { - axisIdx = 0; - } else if (axis == "y2") { - axisIdx = 1; - } else if (axis == "x") { - axisIdx = -1; // simply a placeholder for below. - } else { - throw "Unknown axis " + axis; - } - axisString = axis; - } - - var userAxis = axisIdx == -1 ? this.xAxis_ : this.yAxes_[axisIdx]; - - // Search the user-specified axis option first. - if (userAxis) { - // This condition could be removed if we always set up this.yAxes_ for y2. - var axisOptions = userAxis.options; - if (axisOptions.hasOwnProperty(name)) { - return axisOptions[name]; - } - } - - // User-specified global options second. - // But, hack, ignore globally-specified 'logscale' for 'x' axis declaration. - if (!(axis === 'x' && name === 'logscale')) { - var result = this.getGlobalUser_(name); - if (result !== null) { - return result; - } - } - // Default axis options third. - var defaultAxisOptions = _dygraphDefaultAttrs2['default'].axes[axisString]; - if (defaultAxisOptions.hasOwnProperty(name)) { - return defaultAxisOptions[name]; - } - - // Default global options last. - return this.getGlobalDefault_(name); -}; - -/** - * Get a value for a specific series. If there is no specific value for the series, - * the value for the axis is returned (and afterwards, the global value.) - * - * @param {string} name the name of the option. - * @param {string} series the series to search. - */ -DygraphOptions.prototype.getForSeries = function (name, series) { - // Honors indexes as series. - if (series === this.dygraph_.getHighlightSeries()) { - if (this.highlightSeries_.hasOwnProperty(name)) { - return this.highlightSeries_[name]; - } - } - - if (!this.series_.hasOwnProperty(series)) { - throw "Unknown series: " + series; - } - - var seriesObj = this.series_[series]; - var seriesOptions = seriesObj["options"]; - if (seriesOptions.hasOwnProperty(name)) { - return seriesOptions[name]; - } - - return this.getForAxis(name, seriesObj["yAxis"]); -}; - -/** - * Returns the number of y-axes on the chart. - * @return {number} the number of axes. - */ -DygraphOptions.prototype.numAxes = function () { - return this.yAxes_.length; -}; - -/** - * Return the y-axis for a given series, specified by name. - */ -DygraphOptions.prototype.axisForSeries = function (series) { - return this.series_[series].yAxis; -}; - -/** - * Returns the options for the specified axis. - */ -// TODO(konigsberg): this is y-axis specific. Support the x axis. -DygraphOptions.prototype.axisOptions = function (yAxis) { - return this.yAxes_[yAxis].options; -}; - -/** - * Return the series associated with an axis. - */ -DygraphOptions.prototype.seriesForAxis = function (yAxis) { - return this.yAxes_[yAxis].series; -}; - -/** - * Return the list of all series, in their columnar order. - */ -DygraphOptions.prototype.seriesNames = function () { - return this.labels_; -}; - -// For "production" code, this gets removed by uglifyjs. -if (typeof process !== 'undefined') { - if ("development" != 'production') { - - /** - * Validate all options. - * This requires OPTIONS_REFERENCE, which is only available in debug builds. - * @private - */ - DygraphOptions.prototype.validateOptions_ = function () { - if (typeof _dygraphOptionsReference2['default'] === 'undefined') { - throw 'Called validateOptions_ in prod build.'; - } - - var that = this; - var validateOption = function validateOption(optionName) { - if (!_dygraphOptionsReference2['default'][optionName]) { - that.warnInvalidOption_(optionName); - } - }; - - var optionsDicts = [this.xAxis_.options, this.yAxes_[0].options, this.yAxes_[1] && this.yAxes_[1].options, this.global_, this.user_, this.highlightSeries_]; - var names = this.seriesNames(); - for (var i = 0; i < names.length; i++) { - var name = names[i]; - if (this.series_.hasOwnProperty(name)) { - optionsDicts.push(this.series_[name].options); - } - } - for (var i = 0; i < optionsDicts.length; i++) { - var dict = optionsDicts[i]; - if (!dict) continue; - for (var optionName in dict) { - if (dict.hasOwnProperty(optionName)) { - validateOption(optionName); - } - } - } - }; - - var WARNINGS = {}; // Only show any particular warning once. - - /** - * Logs a warning about invalid options. - * TODO: make this throw for testing - * @private - */ - DygraphOptions.prototype.warnInvalidOption_ = function (optionName) { - if (!WARNINGS[optionName]) { - WARNINGS[optionName] = true; - var isSeries = this.labels_.indexOf(optionName) >= 0; - if (isSeries) { - console.warn('Use new-style per-series options (saw ' + optionName + ' as top-level options key). See http://bit.ly/1tceaJs'); - } else { - console.warn('Unknown option ' + optionName + ' (full list of options at dygraphs.com/options.html'); - } - throw "invalid option " + optionName; - } - }; - - // Reset list of previously-shown warnings. Used for testing. - DygraphOptions.resetWarnings_ = function () { - WARNINGS = {}; - }; - } -} - -exports['default'] = DygraphOptions; -module.exports = exports['default']; - -}).call(this,require('_process')) - -},{"./dygraph-default-attrs":10,"./dygraph-options-reference":14,"./dygraph-utils":17,"_process":1}],16:[function(require,module,exports){ -/** - * @license - * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/** - * @fileoverview Description of this file. - * @author danvk@google.com (Dan Vanderkam) - * - * A ticker is a function with the following interface: - * - * function(a, b, pixels, options_view, dygraph, forced_values); - * -> [ { v: tick1_v, label: tick1_label[, label_v: label_v1] }, - * { v: tick2_v, label: tick2_label[, label_v: label_v2] }, - * ... - * ] - * - * The returned value is called a "tick list". - * - * Arguments - * --------- - * - * [a, b] is the range of the axis for which ticks are being generated. For a - * numeric axis, these will simply be numbers. For a date axis, these will be - * millis since epoch (convertable to Date objects using "new Date(a)" and "new - * Date(b)"). - * - * opts provides access to chart- and axis-specific options. It can be used to - * access number/date formatting code/options, check for a log scale, etc. - * - * pixels is the length of the axis in pixels. opts('pixelsPerLabel') is the - * minimum amount of space to be allotted to each label. For instance, if - * pixels=400 and opts('pixelsPerLabel')=40 then the ticker should return - * between zero and ten (400/40) ticks. - * - * dygraph is the Dygraph object for which an axis is being constructed. - * - * forced_values is used for secondary y-axes. The tick positions are typically - * set by the primary y-axis, so the secondary y-axis has no choice in where to - * put these. It simply has to generate labels for these data values. - * - * Tick lists - * ---------- - * Typically a tick will have both a grid/tick line and a label at one end of - * that line (at the bottom for an x-axis, at left or right for the y-axis). - * - * A tick may be missing one of these two components: - * - If "label_v" is specified instead of "v", then there will be no tick or - * gridline, just a label. - * - Similarly, if "label" is not specified, then there will be a gridline - * without a label. - * - * This flexibility is useful in a few situations: - * - For log scales, some of the tick lines may be too close to all have labels. - * - For date scales where years are being displayed, it is desirable to display - * tick marks at the beginnings of years but labels (e.g. "2006") in the - * middle of the years. - */ - -/*jshint sub:true */ -/*global Dygraph:false */ -"use strict"; - -Object.defineProperty(exports, '__esModule', { - value: true -}); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -var _dygraphUtils = require('./dygraph-utils'); - -var utils = _interopRequireWildcard(_dygraphUtils); - -/** @typedef {Array.<{v:number, label:string, label_v:(string|undefined)}>} */ -var TickList = undefined; // the ' = undefined' keeps jshint happy. - -/** @typedef {function( - * number, - * number, - * number, - * function(string):*, - * Dygraph=, - * Array.= - * ): TickList} - */ -var Ticker = undefined; // the ' = undefined' keeps jshint happy. - -/** @type {Ticker} */ -var numericLinearTicks = function numericLinearTicks(a, b, pixels, opts, dygraph, vals) { - var nonLogscaleOpts = function nonLogscaleOpts(opt) { - if (opt === 'logscale') return false; - return opts(opt); - }; - return numericTicks(a, b, pixels, nonLogscaleOpts, dygraph, vals); -}; - -exports.numericLinearTicks = numericLinearTicks; -/** @type {Ticker} */ -var numericTicks = function numericTicks(a, b, pixels, opts, dygraph, vals) { - var pixels_per_tick = /** @type{number} */opts('pixelsPerLabel'); - var ticks = []; - var i, j, tickV, nTicks; - if (vals) { - for (i = 0; i < vals.length; i++) { - ticks.push({ v: vals[i] }); - } - } else { - // TODO(danvk): factor this log-scale block out into a separate function. - if (opts("logscale")) { - nTicks = Math.floor(pixels / pixels_per_tick); - var minIdx = utils.binarySearch(a, PREFERRED_LOG_TICK_VALUES, 1); - var maxIdx = utils.binarySearch(b, PREFERRED_LOG_TICK_VALUES, -1); - if (minIdx == -1) { - minIdx = 0; - } - if (maxIdx == -1) { - maxIdx = PREFERRED_LOG_TICK_VALUES.length - 1; - } - // Count the number of tick values would appear, if we can get at least - // nTicks / 4 accept them. - var lastDisplayed = null; - if (maxIdx - minIdx >= nTicks / 4) { - for (var idx = maxIdx; idx >= minIdx; idx--) { - var tickValue = PREFERRED_LOG_TICK_VALUES[idx]; - var pixel_coord = Math.log(tickValue / a) / Math.log(b / a) * pixels; - var tick = { v: tickValue }; - if (lastDisplayed === null) { - lastDisplayed = { - tickValue: tickValue, - pixel_coord: pixel_coord - }; - } else { - if (Math.abs(pixel_coord - lastDisplayed.pixel_coord) >= pixels_per_tick) { - lastDisplayed = { - tickValue: tickValue, - pixel_coord: pixel_coord - }; - } else { - tick.label = ""; - } - } - ticks.push(tick); - } - // Since we went in backwards order. - ticks.reverse(); - } - } - - // ticks.length won't be 0 if the log scale function finds values to insert. - if (ticks.length === 0) { - // Basic idea: - // Try labels every 1, 2, 5, 10, 20, 50, 100, etc. - // Calculate the resulting tick spacing (i.e. this.height_ / nTicks). - // The first spacing greater than pixelsPerYLabel is what we use. - // TODO(danvk): version that works on a log scale. - var kmg2 = opts("labelsKMG2"); - var mults, base; - if (kmg2) { - mults = [1, 2, 4, 8, 16, 32, 64, 128, 256]; - base = 16; - } else { - mults = [1, 2, 5, 10, 20, 50, 100]; - base = 10; - } - - // Get the maximum number of permitted ticks based on the - // graph's pixel size and pixels_per_tick setting. - var max_ticks = Math.ceil(pixels / pixels_per_tick); - - // Now calculate the data unit equivalent of this tick spacing. - // Use abs() since graphs may have a reversed Y axis. - var units_per_tick = Math.abs(b - a) / max_ticks; - - // Based on this, get a starting scale which is the largest - // integer power of the chosen base (10 or 16) that still remains - // below the requested pixels_per_tick spacing. - var base_power = Math.floor(Math.log(units_per_tick) / Math.log(base)); - var base_scale = Math.pow(base, base_power); - - // Now try multiples of the starting scale until we find one - // that results in tick marks spaced sufficiently far apart. - // The "mults" array should cover the range 1 .. base^2 to - // adjust for rounding and edge effects. - var scale, low_val, high_val, spacing; - for (j = 0; j < mults.length; j++) { - scale = base_scale * mults[j]; - low_val = Math.floor(a / scale) * scale; - high_val = Math.ceil(b / scale) * scale; - nTicks = Math.abs(high_val - low_val) / scale; - spacing = pixels / nTicks; - if (spacing > pixels_per_tick) break; - } - - // Construct the set of ticks. - // Allow reverse y-axis if it's explicitly requested. - if (low_val > high_val) scale *= -1; - for (i = 0; i <= nTicks; i++) { - tickV = low_val + i * scale; - ticks.push({ v: tickV }); - } - } - } - - var formatter = /**@type{AxisLabelFormatter}*/opts('axisLabelFormatter'); - - // Add labels to the ticks. - for (i = 0; i < ticks.length; i++) { - if (ticks[i].label !== undefined) continue; // Use current label. - // TODO(danvk): set granularity to something appropriate here. - ticks[i].label = formatter.call(dygraph, ticks[i].v, 0, opts, dygraph); - } - - return ticks; -}; - -exports.numericTicks = numericTicks; -/** @type {Ticker} */ -var dateTicker = function dateTicker(a, b, pixels, opts, dygraph, vals) { - var chosen = pickDateTickGranularity(a, b, pixels, opts); - - if (chosen >= 0) { - return getDateAxis(a, b, chosen, opts, dygraph); - } else { - // this can happen if self.width_ is zero. - return []; - } -}; - -exports.dateTicker = dateTicker; -// Time granularity enumeration -var Granularity = { - MILLISECONDLY: 0, - TWO_MILLISECONDLY: 1, - FIVE_MILLISECONDLY: 2, - TEN_MILLISECONDLY: 3, - FIFTY_MILLISECONDLY: 4, - HUNDRED_MILLISECONDLY: 5, - FIVE_HUNDRED_MILLISECONDLY: 6, - SECONDLY: 7, - TWO_SECONDLY: 8, - FIVE_SECONDLY: 9, - TEN_SECONDLY: 10, - THIRTY_SECONDLY: 11, - MINUTELY: 12, - TWO_MINUTELY: 13, - FIVE_MINUTELY: 14, - TEN_MINUTELY: 15, - THIRTY_MINUTELY: 16, - HOURLY: 17, - TWO_HOURLY: 18, - SIX_HOURLY: 19, - DAILY: 20, - TWO_DAILY: 21, - WEEKLY: 22, - MONTHLY: 23, - QUARTERLY: 24, - BIANNUAL: 25, - ANNUAL: 26, - DECADAL: 27, - CENTENNIAL: 28, - NUM_GRANULARITIES: 29 -}; - -exports.Granularity = Granularity; -// Date components enumeration (in the order of the arguments in Date) -// TODO: make this an @enum -var DateField = { - DATEFIELD_Y: 0, - DATEFIELD_M: 1, - DATEFIELD_D: 2, - DATEFIELD_HH: 3, - DATEFIELD_MM: 4, - DATEFIELD_SS: 5, - DATEFIELD_MS: 6, - NUM_DATEFIELDS: 7 -}; - -/** - * The value of datefield will start at an even multiple of "step", i.e. - * if datefield=SS and step=5 then the first tick will be on a multiple of 5s. - * - * For granularities <= HOURLY, ticks are generated every `spacing` ms. - * - * At coarser granularities, ticks are generated by incrementing `datefield` by - * `step`. In this case, the `spacing` value is only used to estimate the - * number of ticks. It should roughly correspond to the spacing between - * adjacent ticks. - * - * @type {Array.<{datefield:number, step:number, spacing:number}>} - */ -var TICK_PLACEMENT = []; -TICK_PLACEMENT[Granularity.MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 1, spacing: 1 }; -TICK_PLACEMENT[Granularity.TWO_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 2, spacing: 2 }; -TICK_PLACEMENT[Granularity.FIVE_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 5, spacing: 5 }; -TICK_PLACEMENT[Granularity.TEN_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 10, spacing: 10 }; -TICK_PLACEMENT[Granularity.FIFTY_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 50, spacing: 50 }; -TICK_PLACEMENT[Granularity.HUNDRED_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 100, spacing: 100 }; -TICK_PLACEMENT[Granularity.FIVE_HUNDRED_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 500, spacing: 500 }; -TICK_PLACEMENT[Granularity.SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 1, spacing: 1000 * 1 }; -TICK_PLACEMENT[Granularity.TWO_SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 2, spacing: 1000 * 2 }; -TICK_PLACEMENT[Granularity.FIVE_SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 5, spacing: 1000 * 5 }; -TICK_PLACEMENT[Granularity.TEN_SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 10, spacing: 1000 * 10 }; -TICK_PLACEMENT[Granularity.THIRTY_SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 30, spacing: 1000 * 30 }; -TICK_PLACEMENT[Granularity.MINUTELY] = { datefield: DateField.DATEFIELD_MM, step: 1, spacing: 1000 * 60 }; -TICK_PLACEMENT[Granularity.TWO_MINUTELY] = { datefield: DateField.DATEFIELD_MM, step: 2, spacing: 1000 * 60 * 2 }; -TICK_PLACEMENT[Granularity.FIVE_MINUTELY] = { datefield: DateField.DATEFIELD_MM, step: 5, spacing: 1000 * 60 * 5 }; -TICK_PLACEMENT[Granularity.TEN_MINUTELY] = { datefield: DateField.DATEFIELD_MM, step: 10, spacing: 1000 * 60 * 10 }; -TICK_PLACEMENT[Granularity.THIRTY_MINUTELY] = { datefield: DateField.DATEFIELD_MM, step: 30, spacing: 1000 * 60 * 30 }; -TICK_PLACEMENT[Granularity.HOURLY] = { datefield: DateField.DATEFIELD_HH, step: 1, spacing: 1000 * 3600 }; -TICK_PLACEMENT[Granularity.TWO_HOURLY] = { datefield: DateField.DATEFIELD_HH, step: 2, spacing: 1000 * 3600 * 2 }; -TICK_PLACEMENT[Granularity.SIX_HOURLY] = { datefield: DateField.DATEFIELD_HH, step: 6, spacing: 1000 * 3600 * 6 }; -TICK_PLACEMENT[Granularity.DAILY] = { datefield: DateField.DATEFIELD_D, step: 1, spacing: 1000 * 86400 }; -TICK_PLACEMENT[Granularity.TWO_DAILY] = { datefield: DateField.DATEFIELD_D, step: 2, spacing: 1000 * 86400 * 2 }; -TICK_PLACEMENT[Granularity.WEEKLY] = { datefield: DateField.DATEFIELD_D, step: 7, spacing: 1000 * 604800 }; -TICK_PLACEMENT[Granularity.MONTHLY] = { datefield: DateField.DATEFIELD_M, step: 1, spacing: 1000 * 7200 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 / 12 -TICK_PLACEMENT[Granularity.QUARTERLY] = { datefield: DateField.DATEFIELD_M, step: 3, spacing: 1000 * 21600 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 / 4 -TICK_PLACEMENT[Granularity.BIANNUAL] = { datefield: DateField.DATEFIELD_M, step: 6, spacing: 1000 * 43200 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 / 2 -TICK_PLACEMENT[Granularity.ANNUAL] = { datefield: DateField.DATEFIELD_Y, step: 1, spacing: 1000 * 86400 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 * 1 -TICK_PLACEMENT[Granularity.DECADAL] = { datefield: DateField.DATEFIELD_Y, step: 10, spacing: 1000 * 864000 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 * 10 -TICK_PLACEMENT[Granularity.CENTENNIAL] = { datefield: DateField.DATEFIELD_Y, step: 100, spacing: 1000 * 8640000 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 * 100 - -/** - * This is a list of human-friendly values at which to show tick marks on a log - * scale. It is k * 10^n, where k=1..9 and n=-39..+39, so: - * ..., 1, 2, 3, 4, 5, ..., 9, 10, 20, 30, ..., 90, 100, 200, 300, ... - * NOTE: this assumes that utils.LOG_SCALE = 10. - * @type {Array.} - */ -var PREFERRED_LOG_TICK_VALUES = (function () { - var vals = []; - for (var power = -39; power <= 39; power++) { - var range = Math.pow(10, power); - for (var mult = 1; mult <= 9; mult++) { - var val = range * mult; - vals.push(val); - } - } - return vals; -})(); - -/** - * Determine the correct granularity of ticks on a date axis. - * - * @param {number} a Left edge of the chart (ms) - * @param {number} b Right edge of the chart (ms) - * @param {number} pixels Size of the chart in the relevant dimension (width). - * @param {function(string):*} opts Function mapping from option name -> value. - * @return {number} The appropriate axis granularity for this chart. See the - * enumeration of possible values in dygraph-tickers.js. - */ -var pickDateTickGranularity = function pickDateTickGranularity(a, b, pixels, opts) { - var pixels_per_tick = /** @type{number} */opts('pixelsPerLabel'); - for (var i = 0; i < Granularity.NUM_GRANULARITIES; i++) { - var num_ticks = numDateTicks(a, b, i); - if (pixels / num_ticks >= pixels_per_tick) { - return i; - } - } - return -1; -}; - -/** - * Compute the number of ticks on a date axis for a given granularity. - * @param {number} start_time - * @param {number} end_time - * @param {number} granularity (one of the granularities enumerated above) - * @return {number} (Approximate) number of ticks that would result. - */ -var numDateTicks = function numDateTicks(start_time, end_time, granularity) { - var spacing = TICK_PLACEMENT[granularity].spacing; - return Math.round(1.0 * (end_time - start_time) / spacing); -}; - -/** - * Compute the positions and labels of ticks on a date axis for a given granularity. - * @param {number} start_time - * @param {number} end_time - * @param {number} granularity (one of the granularities enumerated above) - * @param {function(string):*} opts Function mapping from option name -> value. - * @param {Dygraph=} dg - * @return {!TickList} - */ -var getDateAxis = function getDateAxis(start_time, end_time, granularity, opts, dg) { - var formatter = /** @type{AxisLabelFormatter} */opts("axisLabelFormatter"); - var utc = opts("labelsUTC"); - var accessors = utc ? utils.DateAccessorsUTC : utils.DateAccessorsLocal; - - var datefield = TICK_PLACEMENT[granularity].datefield; - var step = TICK_PLACEMENT[granularity].step; - var spacing = TICK_PLACEMENT[granularity].spacing; - - // Choose a nice tick position before the initial instant. - // Currently, this code deals properly with the existent daily granularities: - // DAILY (with step of 1) and WEEKLY (with step of 7 but specially handled). - // Other daily granularities (say TWO_DAILY) should also be handled specially - // by setting the start_date_offset to 0. - var start_date = new Date(start_time); - var date_array = []; - date_array[DateField.DATEFIELD_Y] = accessors.getFullYear(start_date); - date_array[DateField.DATEFIELD_M] = accessors.getMonth(start_date); - date_array[DateField.DATEFIELD_D] = accessors.getDate(start_date); - date_array[DateField.DATEFIELD_HH] = accessors.getHours(start_date); - date_array[DateField.DATEFIELD_MM] = accessors.getMinutes(start_date); - date_array[DateField.DATEFIELD_SS] = accessors.getSeconds(start_date); - date_array[DateField.DATEFIELD_MS] = accessors.getMilliseconds(start_date); - - var start_date_offset = date_array[datefield] % step; - if (granularity == Granularity.WEEKLY) { - // This will put the ticks on Sundays. - start_date_offset = accessors.getDay(start_date); - } - - date_array[datefield] -= start_date_offset; - for (var df = datefield + 1; df < DateField.NUM_DATEFIELDS; df++) { - // The minimum value is 1 for the day of month, and 0 for all other fields. - date_array[df] = df === DateField.DATEFIELD_D ? 1 : 0; - } - - // Generate the ticks. - // For granularities not coarser than HOURLY we use the fact that: - // the number of milliseconds between ticks is constant - // and equal to the defined spacing. - // Otherwise we rely on the 'roll over' property of the Date functions: - // when some date field is set to a value outside of its logical range, - // the excess 'rolls over' the next (more significant) field. - // However, when using local time with DST transitions, - // there are dates that do not represent any time value at all - // (those in the hour skipped at the 'spring forward'), - // and the JavaScript engines usually return an equivalent value. - // Hence we have to check that the date is properly increased at each step, - // returning a date at a nice tick position. - var ticks = []; - var tick_date = accessors.makeDate.apply(null, date_array); - var tick_time = tick_date.getTime(); - if (granularity <= Granularity.HOURLY) { - if (tick_time < start_time) { - tick_time += spacing; - tick_date = new Date(tick_time); - } - while (tick_time <= end_time) { - ticks.push({ v: tick_time, - label: formatter.call(dg, tick_date, granularity, opts, dg) - }); - tick_time += spacing; - tick_date = new Date(tick_time); - } - } else { - if (tick_time < start_time) { - date_array[datefield] += step; - tick_date = accessors.makeDate.apply(null, date_array); - tick_time = tick_date.getTime(); - } - while (tick_time <= end_time) { - if (granularity >= Granularity.DAILY || accessors.getHours(tick_date) % step === 0) { - ticks.push({ v: tick_time, - label: formatter.call(dg, tick_date, granularity, opts, dg) - }); - } - date_array[datefield] += step; - tick_date = accessors.makeDate.apply(null, date_array); - tick_time = tick_date.getTime(); - } - } - return ticks; -}; -exports.getDateAxis = getDateAxis; - -},{"./dygraph-utils":17}],17:[function(require,module,exports){ -/** - * @license - * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/** - * @fileoverview This file contains utility functions used by dygraphs. These - * are typically static (i.e. not related to any particular dygraph). Examples - * include date/time formatting functions, basic algorithms (e.g. binary - * search) and generic DOM-manipulation functions. - */ - -/*global Dygraph:false, Node:false */ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.removeEvent = removeEvent; -exports.cancelEvent = cancelEvent; -exports.hsvToRGB = hsvToRGB; -exports.findPos = findPos; -exports.pageX = pageX; -exports.pageY = pageY; -exports.dragGetX_ = dragGetX_; -exports.dragGetY_ = dragGetY_; -exports.isOK = isOK; -exports.isValidPoint = isValidPoint; -exports.floatFormat = floatFormat; -exports.zeropad = zeropad; -exports.hmsString_ = hmsString_; -exports.dateString_ = dateString_; -exports.round_ = round_; -exports.binarySearch = binarySearch; -exports.dateParser = dateParser; -exports.dateStrToMillis = dateStrToMillis; -exports.update = update; -exports.updateDeep = updateDeep; -exports.isArrayLike = isArrayLike; -exports.isDateLike = isDateLike; -exports.clone = clone; -exports.createCanvas = createCanvas; -exports.getContextPixelRatio = getContextPixelRatio; -exports.Iterator = Iterator; -exports.createIterator = createIterator; -exports.repeatAndCleanup = repeatAndCleanup; -exports.isPixelChangingOptionList = isPixelChangingOptionList; -exports.detectLineDelimiter = detectLineDelimiter; -exports.isNodeContainedBy = isNodeContainedBy; -exports.pow = pow; -exports.toRGB_ = toRGB_; -exports.isCanvasSupported = isCanvasSupported; -exports.parseFloat_ = parseFloat_; -exports.numberValueFormatter = numberValueFormatter; -exports.numberAxisLabelFormatter = numberAxisLabelFormatter; -exports.dateAxisLabelFormatter = dateAxisLabelFormatter; -exports.dateValueFormatter = dateValueFormatter; - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj["default"] = obj; return newObj; } } - -var _dygraphTickers = require('./dygraph-tickers'); - -var DygraphTickers = _interopRequireWildcard(_dygraphTickers); - -var LOG_SCALE = 10; -exports.LOG_SCALE = LOG_SCALE; -var LN_TEN = Math.log(LOG_SCALE); - -exports.LN_TEN = LN_TEN; -/** - * @private - * @param {number} x - * @return {number} - */ -var log10 = function log10(x) { - return Math.log(x) / LN_TEN; -}; - -exports.log10 = log10; -/** - * @private - * @param {number} r0 - * @param {number} r1 - * @param {number} pct - * @return {number} - */ -var logRangeFraction = function logRangeFraction(r0, r1, pct) { - // Computing the inverse of toPercentXCoord. The function was arrived at with - // the following steps: - // - // Original calcuation: - // pct = (log(x) - log(xRange[0])) / (log(xRange[1]) - log(xRange[0]))); - // - // Multiply both sides by the right-side denominator. - // pct * (log(xRange[1] - log(xRange[0]))) = log(x) - log(xRange[0]) - // - // add log(xRange[0]) to both sides - // log(xRange[0]) + (pct * (log(xRange[1]) - log(xRange[0])) = log(x); - // - // Swap both sides of the equation, - // log(x) = log(xRange[0]) + (pct * (log(xRange[1]) - log(xRange[0])) - // - // Use both sides as the exponent in 10^exp and we're done. - // x = 10 ^ (log(xRange[0]) + (pct * (log(xRange[1]) - log(xRange[0]))) - - var logr0 = log10(r0); - var logr1 = log10(r1); - var exponent = logr0 + pct * (logr1 - logr0); - var value = Math.pow(LOG_SCALE, exponent); - return value; -}; - -exports.logRangeFraction = logRangeFraction; -/** A dotted line stroke pattern. */ -var DOTTED_LINE = [2, 2]; -exports.DOTTED_LINE = DOTTED_LINE; -/** A dashed line stroke pattern. */ -var DASHED_LINE = [7, 3]; -exports.DASHED_LINE = DASHED_LINE; -/** A dot dash stroke pattern. */ -var DOT_DASH_LINE = [7, 2, 2, 2]; - -exports.DOT_DASH_LINE = DOT_DASH_LINE; -// Directions for panning and zooming. Use bit operations when combined -// values are possible. -var HORIZONTAL = 1; -exports.HORIZONTAL = HORIZONTAL; -var VERTICAL = 2; - -exports.VERTICAL = VERTICAL; -/** - * Return the 2d context for a dygraph canvas. - * - * This method is only exposed for the sake of replacing the function in - * automated tests. - * - * @param {!HTMLCanvasElement} canvas - * @return {!CanvasRenderingContext2D} - * @private - */ -var getContext = function getContext(canvas) { - return (/** @type{!CanvasRenderingContext2D}*/canvas.getContext("2d") - ); -}; - -exports.getContext = getContext; -/** - * Add an event handler. - * @param {!Node} elem The element to add the event to. - * @param {string} type The type of the event, e.g. 'click' or 'mousemove'. - * @param {function(Event):(boolean|undefined)} fn The function to call - * on the event. The function takes one parameter: the event object. - * @private - */ -var addEvent = function addEvent(elem, type, fn) { - elem.addEventListener(type, fn, false); -}; - -exports.addEvent = addEvent; -/** - * Remove an event handler. - * @param {!Node} elem The element to remove the event from. - * @param {string} type The type of the event, e.g. 'click' or 'mousemove'. - * @param {function(Event):(boolean|undefined)} fn The function to call - * on the event. The function takes one parameter: the event object. - */ - -function removeEvent(elem, type, fn) { - elem.removeEventListener(type, fn, false); -} - -; - -/** - * Cancels further processing of an event. This is useful to prevent default - * browser actions, e.g. highlighting text on a double-click. - * Based on the article at - * http://www.switchonthecode.com/tutorials/javascript-tutorial-the-scroll-wheel - * @param {!Event} e The event whose normal behavior should be canceled. - * @private - */ - -function cancelEvent(e) { - e = e ? e : window.event; - if (e.stopPropagation) { - e.stopPropagation(); - } - if (e.preventDefault) { - e.preventDefault(); - } - e.cancelBubble = true; - e.cancel = true; - e.returnValue = false; - return false; -} - -; - -/** - * Convert hsv values to an rgb(r,g,b) string. Taken from MochiKit.Color. This - * is used to generate default series colors which are evenly spaced on the - * color wheel. - * @param { number } hue Range is 0.0-1.0. - * @param { number } saturation Range is 0.0-1.0. - * @param { number } value Range is 0.0-1.0. - * @return { string } "rgb(r,g,b)" where r, g and b range from 0-255. - * @private - */ - -function hsvToRGB(hue, saturation, value) { - var red; - var green; - var blue; - if (saturation === 0) { - red = value; - green = value; - blue = value; - } else { - var i = Math.floor(hue * 6); - var f = hue * 6 - i; - var p = value * (1 - saturation); - var q = value * (1 - saturation * f); - var t = value * (1 - saturation * (1 - f)); - switch (i) { - case 1: - red = q;green = value;blue = p;break; - case 2: - red = p;green = value;blue = t;break; - case 3: - red = p;green = q;blue = value;break; - case 4: - red = t;green = p;blue = value;break; - case 5: - red = value;green = p;blue = q;break; - case 6: // fall through - case 0: - red = value;green = t;blue = p;break; - } - } - red = Math.floor(255 * red + 0.5); - green = Math.floor(255 * green + 0.5); - blue = Math.floor(255 * blue + 0.5); - return 'rgb(' + red + ',' + green + ',' + blue + ')'; -} - -; - -/** - * Find the coordinates of an object relative to the top left of the page. - * - * @param {Node} obj - * @return {{x:number,y:number}} - * @private - */ - -function findPos(obj) { - var p = obj.getBoundingClientRect(), - w = window, - d = document.documentElement; - - return { - x: p.left + (w.pageXOffset || d.scrollLeft), - y: p.top + (w.pageYOffset || d.scrollTop) - }; -} - -; - -/** - * Returns the x-coordinate of the event in a coordinate system where the - * top-left corner of the page (not the window) is (0,0). - * Taken from MochiKit.Signal - * @param {!Event} e - * @return {number} - * @private - */ - -function pageX(e) { - return !e.pageX || e.pageX < 0 ? 0 : e.pageX; -} - -; - -/** - * Returns the y-coordinate of the event in a coordinate system where the - * top-left corner of the page (not the window) is (0,0). - * Taken from MochiKit.Signal - * @param {!Event} e - * @return {number} - * @private - */ - -function pageY(e) { - return !e.pageY || e.pageY < 0 ? 0 : e.pageY; -} - -; - -/** - * Converts page the x-coordinate of the event to pixel x-coordinates on the - * canvas (i.e. DOM Coords). - * @param {!Event} e Drag event. - * @param {!DygraphInteractionContext} context Interaction context object. - * @return {number} The amount by which the drag has moved to the right. - */ - -function dragGetX_(e, context) { - return pageX(e) - context.px; -} - -; - -/** - * Converts page the y-coordinate of the event to pixel y-coordinates on the - * canvas (i.e. DOM Coords). - * @param {!Event} e Drag event. - * @param {!DygraphInteractionContext} context Interaction context object. - * @return {number} The amount by which the drag has moved down. - */ - -function dragGetY_(e, context) { - return pageY(e) - context.py; -} - -; - -/** - * This returns true unless the parameter is 0, null, undefined or NaN. - * TODO(danvk): rename this function to something like 'isNonZeroNan'. - * - * @param {number} x The number to consider. - * @return {boolean} Whether the number is zero or NaN. - * @private - */ - -function isOK(x) { - return !!x && !isNaN(x); -} - -; - -/** - * @param {{x:?number,y:?number,yval:?number}} p The point to consider, valid - * points are {x, y} objects - * @param {boolean=} opt_allowNaNY Treat point with y=NaN as valid - * @return {boolean} Whether the point has numeric x and y. - * @private - */ - -function isValidPoint(p, opt_allowNaNY) { - if (!p) return false; // null or undefined object - if (p.yval === null) return false; // missing point - if (p.x === null || p.x === undefined) return false; - if (p.y === null || p.y === undefined) return false; - if (isNaN(p.x) || !opt_allowNaNY && isNaN(p.y)) return false; - return true; -} - -; - -/** - * Number formatting function which mimics the behavior of %g in printf, i.e. - * either exponential or fixed format (without trailing 0s) is used depending on - * the length of the generated string. The advantage of this format is that - * there is a predictable upper bound on the resulting string length, - * significant figures are not dropped, and normal numbers are not displayed in - * exponential notation. - * - * NOTE: JavaScript's native toPrecision() is NOT a drop-in replacement for %g. - * It creates strings which are too long for absolute values between 10^-4 and - * 10^-6, e.g. '0.00001' instead of '1e-5'. See tests/number-format.html for - * output examples. - * - * @param {number} x The number to format - * @param {number=} opt_precision The precision to use, default 2. - * @return {string} A string formatted like %g in printf. The max generated - * string length should be precision + 6 (e.g 1.123e+300). - */ - -function floatFormat(x, opt_precision) { - // Avoid invalid precision values; [1, 21] is the valid range. - var p = Math.min(Math.max(1, opt_precision || 2), 21); - - // This is deceptively simple. The actual algorithm comes from: - // - // Max allowed length = p + 4 - // where 4 comes from 'e+n' and '.'. - // - // Length of fixed format = 2 + y + p - // where 2 comes from '0.' and y = # of leading zeroes. - // - // Equating the two and solving for y yields y = 2, or 0.00xxxx which is - // 1.0e-3. - // - // Since the behavior of toPrecision() is identical for larger numbers, we - // don't have to worry about the other bound. - // - // Finally, the argument for toExponential() is the number of trailing digits, - // so we take off 1 for the value before the '.'. - return Math.abs(x) < 1.0e-3 && x !== 0.0 ? x.toExponential(p - 1) : x.toPrecision(p); -} - -; - -/** - * Converts '9' to '09' (useful for dates) - * @param {number} x - * @return {string} - * @private - */ - -function zeropad(x) { - if (x < 10) return "0" + x;else return "" + x; -} - -; - -/** - * Date accessors to get the parts of a calendar date (year, month, - * day, hour, minute, second and millisecond) according to local time, - * and factory method to call the Date constructor with an array of arguments. - */ -var DateAccessorsLocal = { - getFullYear: function getFullYear(d) { - return d.getFullYear(); - }, - getMonth: function getMonth(d) { - return d.getMonth(); - }, - getDate: function getDate(d) { - return d.getDate(); - }, - getHours: function getHours(d) { - return d.getHours(); - }, - getMinutes: function getMinutes(d) { - return d.getMinutes(); - }, - getSeconds: function getSeconds(d) { - return d.getSeconds(); - }, - getMilliseconds: function getMilliseconds(d) { - return d.getMilliseconds(); - }, - getDay: function getDay(d) { - return d.getDay(); - }, - makeDate: function makeDate(y, m, d, hh, mm, ss, ms) { - return new Date(y, m, d, hh, mm, ss, ms); - } -}; - -exports.DateAccessorsLocal = DateAccessorsLocal; -/** - * Date accessors to get the parts of a calendar date (year, month, - * day of month, hour, minute, second and millisecond) according to UTC time, - * and factory method to call the Date constructor with an array of arguments. - */ -var DateAccessorsUTC = { - getFullYear: function getFullYear(d) { - return d.getUTCFullYear(); - }, - getMonth: function getMonth(d) { - return d.getUTCMonth(); - }, - getDate: function getDate(d) { - return d.getUTCDate(); - }, - getHours: function getHours(d) { - return d.getUTCHours(); - }, - getMinutes: function getMinutes(d) { - return d.getUTCMinutes(); - }, - getSeconds: function getSeconds(d) { - return d.getUTCSeconds(); - }, - getMilliseconds: function getMilliseconds(d) { - return d.getUTCMilliseconds(); - }, - getDay: function getDay(d) { - return d.getUTCDay(); - }, - makeDate: function makeDate(y, m, d, hh, mm, ss, ms) { - return new Date(Date.UTC(y, m, d, hh, mm, ss, ms)); - } -}; - -exports.DateAccessorsUTC = DateAccessorsUTC; -/** - * Return a string version of the hours, minutes and seconds portion of a date. - * @param {number} hh The hours (from 0-23) - * @param {number} mm The minutes (from 0-59) - * @param {number} ss The seconds (from 0-59) - * @return {string} A time of the form "HH:MM" or "HH:MM:SS" - * @private - */ - -function hmsString_(hh, mm, ss, ms) { - var ret = zeropad(hh) + ":" + zeropad(mm); - if (ss) { - ret += ":" + zeropad(ss); - if (ms) { - var str = "" + ms; - ret += "." + ('000' + str).substring(str.length); - } - } - return ret; -} - -; - -/** - * Convert a JS date (millis since epoch) to a formatted string. - * @param {number} time The JavaScript time value (ms since epoch) - * @param {boolean} utc Whether output UTC or local time - * @return {string} A date of one of these forms: - * "YYYY/MM/DD", "YYYY/MM/DD HH:MM" or "YYYY/MM/DD HH:MM:SS" - * @private - */ - -function dateString_(time, utc) { - var accessors = utc ? DateAccessorsUTC : DateAccessorsLocal; - var date = new Date(time); - var y = accessors.getFullYear(date); - var m = accessors.getMonth(date); - var d = accessors.getDate(date); - var hh = accessors.getHours(date); - var mm = accessors.getMinutes(date); - var ss = accessors.getSeconds(date); - var ms = accessors.getMilliseconds(date); - // Get a year string: - var year = "" + y; - // Get a 0 padded month string - var month = zeropad(m + 1); //months are 0-offset, sigh - // Get a 0 padded day string - var day = zeropad(d); - var frac = hh * 3600 + mm * 60 + ss + 1e-3 * ms; - var ret = year + "/" + month + "/" + day; - if (frac) { - ret += " " + hmsString_(hh, mm, ss, ms); - } - return ret; -} - -; - -/** - * Round a number to the specified number of digits past the decimal point. - * @param {number} num The number to round - * @param {number} places The number of decimals to which to round - * @return {number} The rounded number - * @private - */ - -function round_(num, places) { - var shift = Math.pow(10, places); - return Math.round(num * shift) / shift; -} - -; - -/** - * Implementation of binary search over an array. - * Currently does not work when val is outside the range of arry's values. - * @param {number} val the value to search for - * @param {Array.} arry is the value over which to search - * @param {number} abs If abs > 0, find the lowest entry greater than val - * If abs < 0, find the highest entry less than val. - * If abs == 0, find the entry that equals val. - * @param {number=} low The first index in arry to consider (optional) - * @param {number=} high The last index in arry to consider (optional) - * @return {number} Index of the element, or -1 if it isn't found. - * @private - */ - -function binarySearch(_x, _x2, _x3, _x4, _x5) { - var _again = true; - - _function: while (_again) { - var val = _x, - arry = _x2, - abs = _x3, - low = _x4, - high = _x5; - _again = false; - - if (low === null || low === undefined || high === null || high === undefined) { - low = 0; - high = arry.length - 1; - } - if (low > high) { - return -1; - } - if (abs === null || abs === undefined) { - abs = 0; - } - var validIndex = function validIndex(idx) { - return idx >= 0 && idx < arry.length; - }; - var mid = parseInt((low + high) / 2, 10); - var element = arry[mid]; - var idx; - if (element == val) { - return mid; - } else if (element > val) { - if (abs > 0) { - // Accept if element > val, but also if prior element < val. - idx = mid - 1; - if (validIndex(idx) && arry[idx] < val) { - return mid; - } - } - _x = val; - _x2 = arry; - _x3 = abs; - _x4 = low; - _x5 = mid - 1; - _again = true; - validIndex = mid = element = idx = undefined; - continue _function; - } else if (element < val) { - if (abs < 0) { - // Accept if element < val, but also if prior element > val. - idx = mid + 1; - if (validIndex(idx) && arry[idx] > val) { - return mid; - } - } - _x = val; - _x2 = arry; - _x3 = abs; - _x4 = mid + 1; - _x5 = high; - _again = true; - validIndex = mid = element = idx = undefined; - continue _function; - } - return -1; // can't actually happen, but makes closure compiler happy - } -} - -; - -/** - * Parses a date, returning the number of milliseconds since epoch. This can be - * passed in as an xValueParser in the Dygraph constructor. - * TODO(danvk): enumerate formats that this understands. - * - * @param {string} dateStr A date in a variety of possible string formats. - * @return {number} Milliseconds since epoch. - * @private - */ - -function dateParser(dateStr) { - var dateStrSlashed; - var d; - - // Let the system try the format first, with one caveat: - // YYYY-MM-DD[ HH:MM:SS] is interpreted as UTC by a variety of browsers. - // dygraphs displays dates in local time, so this will result in surprising - // inconsistencies. But if you specify "T" or "Z" (i.e. YYYY-MM-DDTHH:MM:SS), - // then you probably know what you're doing, so we'll let you go ahead. - // Issue: http://code.google.com/p/dygraphs/issues/detail?id=255 - if (dateStr.search("-") == -1 || dateStr.search("T") != -1 || dateStr.search("Z") != -1) { - d = dateStrToMillis(dateStr); - if (d && !isNaN(d)) return d; - } - - if (dateStr.search("-") != -1) { - // e.g. '2009-7-12' or '2009-07-12' - dateStrSlashed = dateStr.replace("-", "/", "g"); - while (dateStrSlashed.search("-") != -1) { - dateStrSlashed = dateStrSlashed.replace("-", "/"); - } - d = dateStrToMillis(dateStrSlashed); - } else if (dateStr.length == 8) { - // e.g. '20090712' - // TODO(danvk): remove support for this format. It's confusing. - dateStrSlashed = dateStr.substr(0, 4) + "/" + dateStr.substr(4, 2) + "/" + dateStr.substr(6, 2); - d = dateStrToMillis(dateStrSlashed); - } else { - // Any format that Date.parse will accept, e.g. "2009/07/12" or - // "2009/07/12 12:34:56" - d = dateStrToMillis(dateStr); - } - - if (!d || isNaN(d)) { - console.error("Couldn't parse " + dateStr + " as a date"); - } - return d; -} - -; - -/** - * This is identical to JavaScript's built-in Date.parse() method, except that - * it doesn't get replaced with an incompatible method by aggressive JS - * libraries like MooTools or Joomla. - * @param {string} str The date string, e.g. "2011/05/06" - * @return {number} millis since epoch - * @private - */ - -function dateStrToMillis(str) { - return new Date(str).getTime(); -} - -; - -// These functions are all based on MochiKit. -/** - * Copies all the properties from o to self. - * - * @param {!Object} self - * @param {!Object} o - * @return {!Object} - */ - -function update(self, o) { - if (typeof o != 'undefined' && o !== null) { - for (var k in o) { - if (o.hasOwnProperty(k)) { - self[k] = o[k]; - } - } - } - return self; -} - -; - -/** - * Copies all the properties from o to self. - * - * @param {!Object} self - * @param {!Object} o - * @return {!Object} - * @private - */ - -function updateDeep(self, o) { - // Taken from http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object - function isNode(o) { - return typeof Node === "object" ? o instanceof Node : typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName === "string"; - } - - if (typeof o != 'undefined' && o !== null) { - for (var k in o) { - if (o.hasOwnProperty(k)) { - if (o[k] === null) { - self[k] = null; - } else if (isArrayLike(o[k])) { - self[k] = o[k].slice(); - } else if (isNode(o[k])) { - // DOM objects are shallowly-copied. - self[k] = o[k]; - } else if (typeof o[k] == 'object') { - if (typeof self[k] != 'object' || self[k] === null) { - self[k] = {}; - } - updateDeep(self[k], o[k]); - } else { - self[k] = o[k]; - } - } - } - } - return self; -} - -; - -/** - * @param {*} o - * @return {boolean} - * @private - */ - -function isArrayLike(o) { - var typ = typeof o; - if (typ != 'object' && !(typ == 'function' && typeof o.item == 'function') || o === null || typeof o.length != 'number' || o.nodeType === 3) { - return false; - } - return true; -} - -; - -/** - * @param {Object} o - * @return {boolean} - * @private - */ - -function isDateLike(o) { - if (typeof o != "object" || o === null || typeof o.getTime != 'function') { - return false; - } - return true; -} - -; - -/** - * Note: this only seems to work for arrays. - * @param {!Array} o - * @return {!Array} - * @private - */ - -function clone(o) { - // TODO(danvk): figure out how MochiKit's version works - var r = []; - for (var i = 0; i < o.length; i++) { - if (isArrayLike(o[i])) { - r.push(clone(o[i])); - } else { - r.push(o[i]); - } - } - return r; -} - -; - -/** - * Create a new canvas element. - * - * @return {!HTMLCanvasElement} - * @private - */ - -function createCanvas() { - return document.createElement('canvas'); -} - -; - -/** - * Returns the context's pixel ratio, which is the ratio between the device - * pixel ratio and the backing store ratio. Typically this is 1 for conventional - * displays, and > 1 for HiDPI displays (such as the Retina MBP). - * See http://www.html5rocks.com/en/tutorials/canvas/hidpi/ for more details. - * - * @param {!CanvasRenderingContext2D} context The canvas's 2d context. - * @return {number} The ratio of the device pixel ratio and the backing store - * ratio for the specified context. - */ - -function getContextPixelRatio(context) { - try { - var devicePixelRatio = window.devicePixelRatio; - var backingStoreRatio = context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1; - if (devicePixelRatio !== undefined) { - return devicePixelRatio / backingStoreRatio; - } else { - // At least devicePixelRatio must be defined for this ratio to make sense. - // We default backingStoreRatio to 1: this does not exist on some browsers - // (i.e. desktop Chrome). - return 1; - } - } catch (e) { - return 1; - } -} - -; - -/** - * TODO(danvk): use @template here when it's better supported for classes. - * @param {!Array} array - * @param {number} start - * @param {number} length - * @param {function(!Array,?):boolean=} predicate - * @constructor - */ - -function Iterator(array, start, length, predicate) { - start = start || 0; - length = length || array.length; - this.hasNext = true; // Use to identify if there's another element. - this.peek = null; // Use for look-ahead - this.start_ = start; - this.array_ = array; - this.predicate_ = predicate; - this.end_ = Math.min(array.length, start + length); - this.nextIdx_ = start - 1; // use -1 so initial advance works. - this.next(); // ignoring result. -} - -; - -/** - * @return {Object} - */ -Iterator.prototype.next = function () { - if (!this.hasNext) { - return null; - } - var obj = this.peek; - - var nextIdx = this.nextIdx_ + 1; - var found = false; - while (nextIdx < this.end_) { - if (!this.predicate_ || this.predicate_(this.array_, nextIdx)) { - this.peek = this.array_[nextIdx]; - found = true; - break; - } - nextIdx++; - } - this.nextIdx_ = nextIdx; - if (!found) { - this.hasNext = false; - this.peek = null; - } - return obj; -}; - -/** - * Returns a new iterator over array, between indexes start and - * start + length, and only returns entries that pass the accept function - * - * @param {!Array} array the array to iterate over. - * @param {number} start the first index to iterate over, 0 if absent. - * @param {number} length the number of elements in the array to iterate over. - * This, along with start, defines a slice of the array, and so length - * doesn't imply the number of elements in the iterator when accept doesn't - * always accept all values. array.length when absent. - * @param {function(?):boolean=} opt_predicate a function that takes - * parameters array and idx, which returns true when the element should be - * returned. If omitted, all elements are accepted. - * @private - */ - -function createIterator(array, start, length, opt_predicate) { - return new Iterator(array, start, length, opt_predicate); -} - -; - -// Shim layer with setTimeout fallback. -// From: http://paulirish.com/2011/requestanimationframe-for-smart-animating/ -// Should be called with the window context: -// Dygraph.requestAnimFrame.call(window, function() {}) -var requestAnimFrame = (function () { - return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { - window.setTimeout(callback, 1000 / 60); - }; -})(); - -exports.requestAnimFrame = requestAnimFrame; -/** - * Call a function at most maxFrames times at an attempted interval of - * framePeriodInMillis, then call a cleanup function once. repeatFn is called - * once immediately, then at most (maxFrames - 1) times asynchronously. If - * maxFrames==1, then cleanup_fn() is also called synchronously. This function - * is used to sequence animation. - * @param {function(number)} repeatFn Called repeatedly -- takes the frame - * number (from 0 to maxFrames-1) as an argument. - * @param {number} maxFrames The max number of times to call repeatFn - * @param {number} framePeriodInMillis Max requested time between frames. - * @param {function()} cleanupFn A function to call after all repeatFn calls. - * @private - */ - -function repeatAndCleanup(repeatFn, maxFrames, framePeriodInMillis, cleanupFn) { - var frameNumber = 0; - var previousFrameNumber; - var startTime = new Date().getTime(); - repeatFn(frameNumber); - if (maxFrames == 1) { - cleanupFn(); - return; - } - var maxFrameArg = maxFrames - 1; - - (function loop() { - if (frameNumber >= maxFrames) return; - requestAnimFrame.call(window, function () { - // Determine which frame to draw based on the delay so far. Will skip - // frames if necessary. - var currentTime = new Date().getTime(); - var delayInMillis = currentTime - startTime; - previousFrameNumber = frameNumber; - frameNumber = Math.floor(delayInMillis / framePeriodInMillis); - var frameDelta = frameNumber - previousFrameNumber; - // If we predict that the subsequent repeatFn call will overshoot our - // total frame target, so our last call will cause a stutter, then jump to - // the last call immediately. If we're going to cause a stutter, better - // to do it faster than slower. - var predictOvershootStutter = frameNumber + frameDelta > maxFrameArg; - if (predictOvershootStutter || frameNumber >= maxFrameArg) { - repeatFn(maxFrameArg); // Ensure final call with maxFrameArg. - cleanupFn(); - } else { - if (frameDelta !== 0) { - // Don't call repeatFn with duplicate frames. - repeatFn(frameNumber); - } - loop(); - } - }); - })(); -} - -; - -// A whitelist of options that do not change pixel positions. -var pixelSafeOptions = { - 'annotationClickHandler': true, - 'annotationDblClickHandler': true, - 'annotationMouseOutHandler': true, - 'annotationMouseOverHandler': true, - 'axisLineColor': true, - 'axisLineWidth': true, - 'clickCallback': true, - 'drawCallback': true, - 'drawHighlightPointCallback': true, - 'drawPoints': true, - 'drawPointCallback': true, - 'drawGrid': true, - 'fillAlpha': true, - 'gridLineColor': true, - 'gridLineWidth': true, - 'hideOverlayOnMouseOut': true, - 'highlightCallback': true, - 'highlightCircleSize': true, - 'interactionModel': true, - 'labelsDiv': true, - 'labelsKMB': true, - 'labelsKMG2': true, - 'labelsSeparateLines': true, - 'labelsShowZeroValues': true, - 'legend': true, - 'panEdgeFraction': true, - 'pixelsPerYLabel': true, - 'pointClickCallback': true, - 'pointSize': true, - 'rangeSelectorPlotFillColor': true, - 'rangeSelectorPlotFillGradientColor': true, - 'rangeSelectorPlotStrokeColor': true, - 'rangeSelectorBackgroundStrokeColor': true, - 'rangeSelectorBackgroundLineWidth': true, - 'rangeSelectorPlotLineWidth': true, - 'rangeSelectorForegroundStrokeColor': true, - 'rangeSelectorForegroundLineWidth': true, - 'rangeSelectorAlpha': true, - 'showLabelsOnHighlight': true, - 'showRoller': true, - 'strokeWidth': true, - 'underlayCallback': true, - 'unhighlightCallback': true, - 'zoomCallback': true -}; - -/** - * This function will scan the option list and determine if they - * require us to recalculate the pixel positions of each point. - * TODO: move this into dygraph-options.js - * @param {!Array.} labels a list of options to check. - * @param {!Object} attrs - * @return {boolean} true if the graph needs new points else false. - * @private - */ - -function isPixelChangingOptionList(labels, attrs) { - // Assume that we do not require new points. - // This will change to true if we actually do need new points. - - // Create a dictionary of series names for faster lookup. - // If there are no labels, then the dictionary stays empty. - var seriesNamesDictionary = {}; - if (labels) { - for (var i = 1; i < labels.length; i++) { - seriesNamesDictionary[labels[i]] = true; - } - } - - // Scan through a flat (i.e. non-nested) object of options. - // Returns true/false depending on whether new points are needed. - var scanFlatOptions = function scanFlatOptions(options) { - for (var property in options) { - if (options.hasOwnProperty(property) && !pixelSafeOptions[property]) { - return true; - } - } - return false; - }; - - // Iterate through the list of updated options. - for (var property in attrs) { - if (!attrs.hasOwnProperty(property)) continue; - - // Find out of this field is actually a series specific options list. - if (property == 'highlightSeriesOpts' || seriesNamesDictionary[property] && !attrs.series) { - // This property value is a list of options for this series. - if (scanFlatOptions(attrs[property])) return true; - } else if (property == 'series' || property == 'axes') { - // This is twice-nested options list. - var perSeries = attrs[property]; - for (var series in perSeries) { - if (perSeries.hasOwnProperty(series) && scanFlatOptions(perSeries[series])) { - return true; - } - } - } else { - // If this was not a series specific option list, check if it's a pixel - // changing property. - if (!pixelSafeOptions[property]) return true; - } - } - - return false; -} - -; - -var Circles = { - DEFAULT: function DEFAULT(g, name, ctx, canvasx, canvasy, color, radius) { - ctx.beginPath(); - ctx.fillStyle = color; - ctx.arc(canvasx, canvasy, radius, 0, 2 * Math.PI, false); - ctx.fill(); - } - // For more shapes, include extras/shapes.js -}; - -exports.Circles = Circles; -/** - * Determine whether |data| is delimited by CR, CRLF, LF, LFCR. - * @param {string} data - * @return {?string} the delimiter that was detected (or null on failure). - */ - -function detectLineDelimiter(data) { - for (var i = 0; i < data.length; i++) { - var code = data.charAt(i); - if (code === '\r') { - // Might actually be "\r\n". - if (i + 1 < data.length && data.charAt(i + 1) === '\n') { - return '\r\n'; - } - return code; - } - if (code === '\n') { - // Might actually be "\n\r". - if (i + 1 < data.length && data.charAt(i + 1) === '\r') { - return '\n\r'; - } - return code; - } - } - - return null; -} - -; - -/** - * Is one node contained by another? - * @param {Node} containee The contained node. - * @param {Node} container The container node. - * @return {boolean} Whether containee is inside (or equal to) container. - * @private - */ - -function isNodeContainedBy(containee, container) { - if (container === null || containee === null) { - return false; - } - var containeeNode = /** @type {Node} */containee; - while (containeeNode && containeeNode !== container) { - containeeNode = containeeNode.parentNode; - } - return containeeNode === container; -} - -; - -// This masks some numeric issues in older versions of Firefox, -// where 1.0/Math.pow(10,2) != Math.pow(10,-2). -/** @type {function(number,number):number} */ - -function pow(base, exp) { - if (exp < 0) { - return 1.0 / Math.pow(base, -exp); - } - return Math.pow(base, exp); -} - -; - -var RGBA_RE = /^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*([01](?:\.\d+)?))?\)$/; - -/** - * Helper for toRGB_ which parses strings of the form: - * rgb(123, 45, 67) - * rgba(123, 45, 67, 0.5) - * @return parsed {r,g,b,a?} tuple or null. - */ -function parseRGBA(rgbStr) { - var bits = RGBA_RE.exec(rgbStr); - if (!bits) return null; - var r = parseInt(bits[1], 10), - g = parseInt(bits[2], 10), - b = parseInt(bits[3], 10); - if (bits[4]) { - return { r: r, g: g, b: b, a: parseFloat(bits[4]) }; - } else { - return { r: r, g: g, b: b }; - } -} - -/** - * Converts any valid CSS color (hex, rgb(), named color) to an RGB tuple. - * - * @param {!string} colorStr Any valid CSS color string. - * @return {{r:number,g:number,b:number,a:number?}} Parsed RGB tuple. - * @private - */ - -function toRGB_(colorStr) { - // Strategy: First try to parse colorStr directly. This is fast & avoids DOM - // manipulation. If that fails (e.g. for named colors like 'red'), then - // create a hidden DOM element and parse its computed color. - var rgb = parseRGBA(colorStr); - if (rgb) return rgb; - - var div = document.createElement('div'); - div.style.backgroundColor = colorStr; - div.style.visibility = 'hidden'; - document.body.appendChild(div); - var rgbStr = window.getComputedStyle(div, null).backgroundColor; - document.body.removeChild(div); - return parseRGBA(rgbStr); -} - -; - -/** - * Checks whether the browser supports the <canvas> tag. - * @param {HTMLCanvasElement=} opt_canvasElement Pass a canvas element as an - * optimization if you have one. - * @return {boolean} Whether the browser supports canvas. - */ - -function isCanvasSupported(opt_canvasElement) { - try { - var canvas = opt_canvasElement || document.createElement("canvas"); - canvas.getContext("2d"); - } catch (e) { - return false; - } - return true; -} - -; - -/** - * Parses the value as a floating point number. This is like the parseFloat() - * built-in, but with a few differences: - * - the empty string is parsed as null, rather than NaN. - * - if the string cannot be parsed at all, an error is logged. - * If the string can't be parsed, this method returns null. - * @param {string} x The string to be parsed - * @param {number=} opt_line_no The line number from which the string comes. - * @param {string=} opt_line The text of the line from which the string comes. - */ - -function parseFloat_(x, opt_line_no, opt_line) { - var val = parseFloat(x); - if (!isNaN(val)) return val; - - // Try to figure out what happeend. - // If the value is the empty string, parse it as null. - if (/^ *$/.test(x)) return null; - - // If it was actually "NaN", return it as NaN. - if (/^ *nan *$/i.test(x)) return NaN; - - // Looks like a parsing error. - var msg = "Unable to parse '" + x + "' as a number"; - if (opt_line !== undefined && opt_line_no !== undefined) { - msg += " on line " + (1 + (opt_line_no || 0)) + " ('" + opt_line + "') of CSV."; - } - console.error(msg); - - return null; -} - -; - -// Label constants for the labelsKMB and labelsKMG2 options. -// (i.e. '100000' -> '100K') -var KMB_LABELS = ['K', 'M', 'B', 'T', 'Q']; -var KMG2_BIG_LABELS = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; -var KMG2_SMALL_LABELS = ['m', 'u', 'n', 'p', 'f', 'a', 'z', 'y']; - -/** - * @private - * Return a string version of a number. This respects the digitsAfterDecimal - * and maxNumberWidth options. - * @param {number} x The number to be formatted - * @param {Dygraph} opts An options view - */ - -function numberValueFormatter(x, opts) { - var sigFigs = opts('sigFigs'); - - if (sigFigs !== null) { - // User has opted for a fixed number of significant figures. - return floatFormat(x, sigFigs); - } - - var digits = opts('digitsAfterDecimal'); - var maxNumberWidth = opts('maxNumberWidth'); - - var kmb = opts('labelsKMB'); - var kmg2 = opts('labelsKMG2'); - - var label; - - // switch to scientific notation if we underflow or overflow fixed display. - if (x !== 0.0 && (Math.abs(x) >= Math.pow(10, maxNumberWidth) || Math.abs(x) < Math.pow(10, -digits))) { - label = x.toExponential(digits); - } else { - label = '' + round_(x, digits); - } - - if (kmb || kmg2) { - var k; - var k_labels = []; - var m_labels = []; - if (kmb) { - k = 1000; - k_labels = KMB_LABELS; - } - if (kmg2) { - if (kmb) console.warn("Setting both labelsKMB and labelsKMG2. Pick one!"); - k = 1024; - k_labels = KMG2_BIG_LABELS; - m_labels = KMG2_SMALL_LABELS; - } - - var absx = Math.abs(x); - var n = pow(k, k_labels.length); - for (var j = k_labels.length - 1; j >= 0; j--, n /= k) { - if (absx >= n) { - label = round_(x / n, digits) + k_labels[j]; - break; - } - } - if (kmg2) { - // TODO(danvk): clean up this logic. Why so different than kmb? - var x_parts = String(x.toExponential()).split('e-'); - if (x_parts.length === 2 && x_parts[1] >= 3 && x_parts[1] <= 24) { - if (x_parts[1] % 3 > 0) { - label = round_(x_parts[0] / pow(10, x_parts[1] % 3), digits); - } else { - label = Number(x_parts[0]).toFixed(2); - } - label += m_labels[Math.floor(x_parts[1] / 3) - 1]; - } - } - } - - return label; -} - -; - -/** - * variant for use as an axisLabelFormatter. - * @private - */ - -function numberAxisLabelFormatter(x, granularity, opts) { - return numberValueFormatter.call(this, x, opts); -} - -; - -/** - * @type {!Array.} - * @private - * @constant - */ -var SHORT_MONTH_NAMES_ = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; - -/** - * Convert a JS date to a string appropriate to display on an axis that - * is displaying values at the stated granularity. This respects the - * labelsUTC option. - * @param {Date} date The date to format - * @param {number} granularity One of the Dygraph granularity constants - * @param {Dygraph} opts An options view - * @return {string} The date formatted as local time - * @private - */ - -function dateAxisLabelFormatter(date, granularity, opts) { - var utc = opts('labelsUTC'); - var accessors = utc ? DateAccessorsUTC : DateAccessorsLocal; - - var year = accessors.getFullYear(date), - month = accessors.getMonth(date), - day = accessors.getDate(date), - hours = accessors.getHours(date), - mins = accessors.getMinutes(date), - secs = accessors.getSeconds(date), - millis = accessors.getMilliseconds(date); - - if (granularity >= DygraphTickers.Granularity.DECADAL) { - return '' + year; - } else if (granularity >= DygraphTickers.Granularity.MONTHLY) { - return SHORT_MONTH_NAMES_[month] + ' ' + year; - } else { - var frac = hours * 3600 + mins * 60 + secs + 1e-3 * millis; - if (frac === 0 || granularity >= DygraphTickers.Granularity.DAILY) { - // e.g. '21 Jan' (%d%b) - return zeropad(day) + ' ' + SHORT_MONTH_NAMES_[month]; - } else if (granularity < DygraphTickers.Granularity.SECONDLY) { - // e.g. 40.310 (meaning 40 seconds and 310 milliseconds) - var str = "" + millis; - return zeropad(secs) + "." + ('000' + str).substring(str.length); - } else if (granularity > DygraphTickers.Granularity.MINUTELY) { - return hmsString_(hours, mins, secs, 0); - } else { - return hmsString_(hours, mins, secs, millis); - } - } -} - -; -// alias in case anyone is referencing the old method. -// Dygraph.dateAxisFormatter = Dygraph.dateAxisLabelFormatter; - -/** - * Return a string version of a JS date for a value label. This respects the - * labelsUTC option. - * @param {Date} date The date to be formatted - * @param {Dygraph} opts An options view - * @private - */ - -function dateValueFormatter(d, opts) { - return dateString_(d, opts('labelsUTC')); -} - -; - -},{"./dygraph-tickers":16}],18:[function(require,module,exports){ -(function (process){ -/** - * @license - * Copyright 2006 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ /** - * @fileoverview Creates an interactive, zoomable graph based on a CSV file or - * string. Dygraph can handle multiple series with or without error bars. The - * date/value ranges will be automatically set. Dygraph uses the - * <canvas> tag, so it only works in FF1.5+. - * @author danvdk@gmail.com (Dan Vanderkam) - - Usage: -
- - - The CSV file is of the form - - Date,SeriesA,SeriesB,SeriesC - YYYYMMDD,A1,B1,C1 - YYYYMMDD,A2,B2,C2 - - If the 'errorBars' option is set in the constructor, the input should be of - the form - Date,SeriesA,SeriesB,... - YYYYMMDD,A1,sigmaA1,B1,sigmaB1,... - YYYYMMDD,A2,sigmaA2,B2,sigmaB2,... - - If the 'fractions' option is set, the input should be of the form: - - Date,SeriesA,SeriesB,... - YYYYMMDD,A1/B1,A2/B2,... - YYYYMMDD,A1/B1,A2/B2,... - - And error bars will be calculated automatically using a binomial distribution. - - For further documentation and examples, see http://dygraphs.com/ - */'use strict';Object.defineProperty(exports,'__esModule',{value:true});var _slicedToArray=(function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n = (_s = _i.next()).done);_n = true) {_arr.push(_s.value);if(i && _arr.length === i)break;}}catch(err) {_d = true;_e = err;}finally {try{if(!_n && _i['return'])_i['return']();}finally {if(_d)throw _e;}}return _arr;}return function(arr,i){if(Array.isArray(arr)){return arr;}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i);}else {throw new TypeError('Invalid attempt to destructure non-iterable instance');}};})();function _interopRequireWildcard(obj){if(obj && obj.__esModule){return obj;}else {var newObj={};if(obj != null){for(var key in obj) {if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key] = obj[key];}}newObj['default'] = obj;return newObj;}}function _interopRequireDefault(obj){return obj && obj.__esModule?obj:{'default':obj};}var _dygraphLayout=require('./dygraph-layout');var _dygraphLayout2=_interopRequireDefault(_dygraphLayout);var _dygraphCanvas=require('./dygraph-canvas');var _dygraphCanvas2=_interopRequireDefault(_dygraphCanvas);var _dygraphOptions=require('./dygraph-options');var _dygraphOptions2=_interopRequireDefault(_dygraphOptions);var _dygraphInteractionModel=require('./dygraph-interaction-model');var _dygraphInteractionModel2=_interopRequireDefault(_dygraphInteractionModel);var _dygraphTickers=require('./dygraph-tickers');var DygraphTickers=_interopRequireWildcard(_dygraphTickers);var _dygraphUtils=require('./dygraph-utils');var utils=_interopRequireWildcard(_dygraphUtils);var _dygraphDefaultAttrs=require('./dygraph-default-attrs');var _dygraphDefaultAttrs2=_interopRequireDefault(_dygraphDefaultAttrs);var _dygraphOptionsReference=require('./dygraph-options-reference');var _dygraphOptionsReference2=_interopRequireDefault(_dygraphOptionsReference);var _iframeTarp=require('./iframe-tarp');var _iframeTarp2=_interopRequireDefault(_iframeTarp);var _datahandlerDefault=require('./datahandler/default');var _datahandlerDefault2=_interopRequireDefault(_datahandlerDefault);var _datahandlerBarsError=require('./datahandler/bars-error');var _datahandlerBarsError2=_interopRequireDefault(_datahandlerBarsError);var _datahandlerBarsCustom=require('./datahandler/bars-custom');var _datahandlerBarsCustom2=_interopRequireDefault(_datahandlerBarsCustom);var _datahandlerDefaultFractions=require('./datahandler/default-fractions');var _datahandlerDefaultFractions2=_interopRequireDefault(_datahandlerDefaultFractions);var _datahandlerBarsFractions=require('./datahandler/bars-fractions');var _datahandlerBarsFractions2=_interopRequireDefault(_datahandlerBarsFractions);var _datahandlerBars=require('./datahandler/bars');var _datahandlerBars2=_interopRequireDefault(_datahandlerBars);var _pluginsAnnotations=require('./plugins/annotations');var _pluginsAnnotations2=_interopRequireDefault(_pluginsAnnotations);var _pluginsAxes=require('./plugins/axes');var _pluginsAxes2=_interopRequireDefault(_pluginsAxes);var _pluginsChartLabels=require('./plugins/chart-labels');var _pluginsChartLabels2=_interopRequireDefault(_pluginsChartLabels);var _pluginsGrid=require('./plugins/grid');var _pluginsGrid2=_interopRequireDefault(_pluginsGrid);var _pluginsLegend=require('./plugins/legend');var _pluginsLegend2=_interopRequireDefault(_pluginsLegend);var _pluginsRangeSelector=require('./plugins/range-selector');var _pluginsRangeSelector2=_interopRequireDefault(_pluginsRangeSelector);var _dygraphGviz=require('./dygraph-gviz');var _dygraphGviz2=_interopRequireDefault(_dygraphGviz);"use strict"; /** - * Creates an interactive, zoomable chart. - * - * @constructor - * @param {div | String} div A div or the id of a div into which to construct - * the chart. - * @param {String | Function} file A file containing CSV data or a function - * that returns this data. The most basic expected format for each line is - * "YYYY/MM/DD,val1,val2,...". For more information, see - * http://dygraphs.com/data.html. - * @param {Object} attrs Various other attributes, e.g. errorBars determines - * whether the input data contains error ranges. For a complete list of - * options, see http://dygraphs.com/options.html. - */var Dygraph=function Dygraph(div,data,opts){this.__init__(div,data,opts);};Dygraph.NAME = "Dygraph";Dygraph.VERSION = "2.1.0"; // Various default values -Dygraph.DEFAULT_ROLL_PERIOD = 1;Dygraph.DEFAULT_WIDTH = 480;Dygraph.DEFAULT_HEIGHT = 320; // For max 60 Hz. animation: -Dygraph.ANIMATION_STEPS = 12;Dygraph.ANIMATION_DURATION = 200; /** - * Standard plotters. These may be used by clients. - * Available plotters are: - * - Dygraph.Plotters.linePlotter: draws central lines (most common) - * - Dygraph.Plotters.errorPlotter: draws error bars - * - Dygraph.Plotters.fillPlotter: draws fills under lines (used with fillGraph) - * - * By default, the plotter is [fillPlotter, errorPlotter, linePlotter]. - * This causes all the lines to be drawn over all the fills/error bars. - */Dygraph.Plotters = _dygraphCanvas2['default']._Plotters; // Used for initializing annotation CSS rules only once. -Dygraph.addedAnnotationCSS = false; /** - * Initializes the Dygraph. This creates a new DIV and constructs the PlotKit - * and context <canvas> inside of it. See the constructor for details. - * on the parameters. - * @param {Element} div the Element to render the graph into. - * @param {string | Function} file Source data - * @param {Object} attrs Miscellaneous other options - * @private - */Dygraph.prototype.__init__ = function(div,file,attrs){this.is_initial_draw_ = true;this.readyFns_ = []; // Support two-argument constructor -if(attrs === null || attrs === undefined){attrs = {};}attrs = Dygraph.copyUserAttrs_(attrs);if(typeof div == 'string'){div = document.getElementById(div);}if(!div){throw new Error('Constructing dygraph with a non-existent div!');} // Copy the important bits into the object -// TODO(danvk): most of these should just stay in the attrs_ dictionary. -this.maindiv_ = div;this.file_ = file;this.rollPeriod_ = attrs.rollPeriod || Dygraph.DEFAULT_ROLL_PERIOD;this.previousVerticalX_ = -1;this.fractions_ = attrs.fractions || false;this.dateWindow_ = attrs.dateWindow || null;this.annotations_ = []; // Clear the div. This ensure that, if multiple dygraphs are passed the same -// div, then only one will be drawn. -div.innerHTML = ""; // For historical reasons, the 'width' and 'height' options trump all CSS -// rules _except_ for an explicit 'width' or 'height' on the div. -// As an added convenience, if the div has zero height (like
does -// without any styles), then we use a default height/width. -if(div.style.width === '' && attrs.width){div.style.width = attrs.width + "px";}if(div.style.height === '' && attrs.height){div.style.height = attrs.height + "px";}if(div.style.height === '' && div.clientHeight === 0){div.style.height = Dygraph.DEFAULT_HEIGHT + "px";if(div.style.width === ''){div.style.width = Dygraph.DEFAULT_WIDTH + "px";}} // These will be zero if the dygraph's div is hidden. In that case, -// use the user-specified attributes if present. If not, use zero -// and assume the user will call resize to fix things later. -this.width_ = div.clientWidth || attrs.width || 0;this.height_ = div.clientHeight || attrs.height || 0; // TODO(danvk): set fillGraph to be part of attrs_ here, not user_attrs_. -if(attrs.stackedGraph){attrs.fillGraph = true; // TODO(nikhilk): Add any other stackedGraph checks here. -} // DEPRECATION WARNING: All option processing should be moved from -// attrs_ and user_attrs_ to options_, which holds all this information. -// -// Dygraphs has many options, some of which interact with one another. -// To keep track of everything, we maintain two sets of options: -// -// this.user_attrs_ only options explicitly set by the user. -// this.attrs_ defaults, options derived from user_attrs_, data. -// -// Options are then accessed this.attr_('attr'), which first looks at -// user_attrs_ and then computed attrs_. This way Dygraphs can set intelligent -// defaults without overriding behavior that the user specifically asks for. -this.user_attrs_ = {};utils.update(this.user_attrs_,attrs); // This sequence ensures that Dygraph.DEFAULT_ATTRS is never modified. -this.attrs_ = {};utils.updateDeep(this.attrs_,_dygraphDefaultAttrs2['default']);this.boundaryIds_ = [];this.setIndexByName_ = {};this.datasetIndex_ = [];this.registeredEvents_ = [];this.eventListeners_ = {};this.attributes_ = new _dygraphOptions2['default'](this); // Create the containing DIV and other interactive elements -this.createInterface_(); // Activate plugins. -this.plugins_ = [];var plugins=Dygraph.PLUGINS.concat(this.getOption('plugins'));for(var i=0;i < plugins.length;i++) { // the plugins option may contain either plugin classes or instances. -// Plugin instances contain an activate method. -var Plugin=plugins[i]; // either a constructor or an instance. -var pluginInstance;if(typeof Plugin.activate !== 'undefined'){pluginInstance = Plugin;}else {pluginInstance = new Plugin();}var pluginDict={plugin:pluginInstance,events:{},options:{},pluginOptions:{}};var handlers=pluginInstance.activate(this);for(var eventName in handlers) {if(!handlers.hasOwnProperty(eventName))continue; // TODO(danvk): validate eventName. -pluginDict.events[eventName] = handlers[eventName];}this.plugins_.push(pluginDict);} // At this point, plugins can no longer register event handlers. -// Construct a map from event -> ordered list of [callback, plugin]. -for(var i=0;i < this.plugins_.length;i++) {var plugin_dict=this.plugins_[i];for(var eventName in plugin_dict.events) {if(!plugin_dict.events.hasOwnProperty(eventName))continue;var callback=plugin_dict.events[eventName];var pair=[plugin_dict.plugin,callback];if(!(eventName in this.eventListeners_)){this.eventListeners_[eventName] = [pair];}else {this.eventListeners_[eventName].push(pair);}}}this.createDragInterface_();this.start_();}; /** - * Triggers a cascade of events to the various plugins which are interested in them. - * Returns true if the "default behavior" should be prevented, i.e. if one - * of the event listeners called event.preventDefault(). - * @private - */Dygraph.prototype.cascadeEvents_ = function(name,extra_props){if(!(name in this.eventListeners_))return false; // QUESTION: can we use objects & prototypes to speed this up? -var e={dygraph:this,cancelable:false,defaultPrevented:false,preventDefault:function preventDefault(){if(!e.cancelable)throw "Cannot call preventDefault on non-cancelable event.";e.defaultPrevented = true;},propagationStopped:false,stopPropagation:function stopPropagation(){e.propagationStopped = true;}};utils.update(e,extra_props);var callback_plugin_pairs=this.eventListeners_[name];if(callback_plugin_pairs){for(var i=callback_plugin_pairs.length - 1;i >= 0;i--) {var plugin=callback_plugin_pairs[i][0];var callback=callback_plugin_pairs[i][1];callback.call(plugin,e);if(e.propagationStopped)break;}}return e.defaultPrevented;}; /** - * Fetch a plugin instance of a particular class. Only for testing. - * @private - * @param {!Class} type The type of the plugin. - * @return {Object} Instance of the plugin, or null if there is none. - */Dygraph.prototype.getPluginInstance_ = function(type){for(var i=0;i < this.plugins_.length;i++) {var p=this.plugins_[i];if(p.plugin instanceof type){return p.plugin;}}return null;}; /** - * Returns the zoomed status of the chart for one or both axes. - * - * Axis is an optional parameter. Can be set to 'x' or 'y'. - * - * The zoomed status for an axis is set whenever a user zooms using the mouse - * or when the dateWindow or valueRange are updated. Double-clicking or calling - * resetZoom() resets the zoom status for the chart. - */Dygraph.prototype.isZoomed = function(axis){var isZoomedX=!!this.dateWindow_;if(axis === 'x')return isZoomedX;var isZoomedY=this.axes_.map(function(axis){return !!axis.valueRange;}).indexOf(true) >= 0;if(axis === null || axis === undefined){return isZoomedX || isZoomedY;}if(axis === 'y')return isZoomedY;throw new Error('axis parameter is [' + axis + '] must be null, \'x\' or \'y\'.');}; /** - * Returns information about the Dygraph object, including its containing ID. - */Dygraph.prototype.toString = function(){var maindiv=this.maindiv_;var id=maindiv && maindiv.id?maindiv.id:maindiv;return "[Dygraph " + id + "]";}; /** - * @private - * Returns the value of an option. This may be set by the user (either in the - * constructor or by calling updateOptions) or by dygraphs, and may be set to a - * per-series value. - * @param {string} name The name of the option, e.g. 'rollPeriod'. - * @param {string} [seriesName] The name of the series to which the option - * will be applied. If no per-series value of this option is available, then - * the global value is returned. This is optional. - * @return { ... } The value of the option. - */Dygraph.prototype.attr_ = function(name,seriesName){ // For "production" code, this gets removed by uglifyjs. -if(typeof process !== 'undefined'){if("development" != 'production'){if(typeof _dygraphOptionsReference2['default'] === 'undefined'){console.error('Must include options reference JS for testing');}else if(!_dygraphOptionsReference2['default'].hasOwnProperty(name)){console.error('Dygraphs is using property ' + name + ', which has no ' + 'entry in the Dygraphs.OPTIONS_REFERENCE listing.'); // Only log this error once. -_dygraphOptionsReference2['default'][name] = true;}}}return seriesName?this.attributes_.getForSeries(name,seriesName):this.attributes_.get(name);}; /** - * Returns the current value for an option, as set in the constructor or via - * updateOptions. You may pass in an (optional) series name to get per-series - * values for the option. - * - * All values returned by this method should be considered immutable. If you - * modify them, there is no guarantee that the changes will be honored or that - * dygraphs will remain in a consistent state. If you want to modify an option, - * use updateOptions() instead. - * - * @param {string} name The name of the option (e.g. 'strokeWidth') - * @param {string=} opt_seriesName Series name to get per-series values. - * @return {*} The value of the option. - */Dygraph.prototype.getOption = function(name,opt_seriesName){return this.attr_(name,opt_seriesName);}; /** - * Like getOption(), but specifically returns a number. - * This is a convenience function for working with the Closure Compiler. - * @param {string} name The name of the option (e.g. 'strokeWidth') - * @param {string=} opt_seriesName Series name to get per-series values. - * @return {number} The value of the option. - * @private - */Dygraph.prototype.getNumericOption = function(name,opt_seriesName){return (/** @type{number} */this.getOption(name,opt_seriesName));}; /** - * Like getOption(), but specifically returns a string. - * This is a convenience function for working with the Closure Compiler. - * @param {string} name The name of the option (e.g. 'strokeWidth') - * @param {string=} opt_seriesName Series name to get per-series values. - * @return {string} The value of the option. - * @private - */Dygraph.prototype.getStringOption = function(name,opt_seriesName){return (/** @type{string} */this.getOption(name,opt_seriesName));}; /** - * Like getOption(), but specifically returns a boolean. - * This is a convenience function for working with the Closure Compiler. - * @param {string} name The name of the option (e.g. 'strokeWidth') - * @param {string=} opt_seriesName Series name to get per-series values. - * @return {boolean} The value of the option. - * @private - */Dygraph.prototype.getBooleanOption = function(name,opt_seriesName){return (/** @type{boolean} */this.getOption(name,opt_seriesName));}; /** - * Like getOption(), but specifically returns a function. - * This is a convenience function for working with the Closure Compiler. - * @param {string} name The name of the option (e.g. 'strokeWidth') - * @param {string=} opt_seriesName Series name to get per-series values. - * @return {function(...)} The value of the option. - * @private - */Dygraph.prototype.getFunctionOption = function(name,opt_seriesName){return (/** @type{function(...)} */this.getOption(name,opt_seriesName));};Dygraph.prototype.getOptionForAxis = function(name,axis){return this.attributes_.getForAxis(name,axis);}; /** - * @private - * @param {string} axis The name of the axis (i.e. 'x', 'y' or 'y2') - * @return { ... } A function mapping string -> option value - */Dygraph.prototype.optionsViewForAxis_ = function(axis){var self=this;return function(opt){var axis_opts=self.user_attrs_.axes;if(axis_opts && axis_opts[axis] && axis_opts[axis].hasOwnProperty(opt)){return axis_opts[axis][opt];} // I don't like that this is in a second spot. -if(axis === 'x' && opt === 'logscale'){ // return the default value. -// TODO(konigsberg): pull the default from a global default. -return false;} // user-specified attributes always trump defaults, even if they're less -// specific. -if(typeof self.user_attrs_[opt] != 'undefined'){return self.user_attrs_[opt];}axis_opts = self.attrs_.axes;if(axis_opts && axis_opts[axis] && axis_opts[axis].hasOwnProperty(opt)){return axis_opts[axis][opt];} // check old-style axis options -// TODO(danvk): add a deprecation warning if either of these match. -if(axis == 'y' && self.axes_[0].hasOwnProperty(opt)){return self.axes_[0][opt];}else if(axis == 'y2' && self.axes_[1].hasOwnProperty(opt)){return self.axes_[1][opt];}return self.attr_(opt);};}; /** - * Returns the current rolling period, as set by the user or an option. - * @return {number} The number of points in the rolling window - */Dygraph.prototype.rollPeriod = function(){return this.rollPeriod_;}; /** - * Returns the currently-visible x-range. This can be affected by zooming, - * panning or a call to updateOptions. - * Returns a two-element array: [left, right]. - * If the Dygraph has dates on the x-axis, these will be millis since epoch. - */Dygraph.prototype.xAxisRange = function(){return this.dateWindow_?this.dateWindow_:this.xAxisExtremes();}; /** - * Returns the lower- and upper-bound x-axis values of the data set. - */Dygraph.prototype.xAxisExtremes = function(){var pad=this.getNumericOption('xRangePad') / this.plotter_.area.w;if(this.numRows() === 0){return [0 - pad,1 + pad];}var left=this.rawData_[0][0];var right=this.rawData_[this.rawData_.length - 1][0];if(pad){ // Must keep this in sync with dygraph-layout _evaluateLimits() -var range=right - left;left -= range * pad;right += range * pad;}return [left,right];}; /** - * Returns the lower- and upper-bound y-axis values for each axis. These are - * the ranges you'll get if you double-click to zoom out or call resetZoom(). - * The return value is an array of [low, high] tuples, one for each y-axis. - */Dygraph.prototype.yAxisExtremes = function(){ // TODO(danvk): this is pretty inefficient -var packed=this.gatherDatasets_(this.rolledSeries_,null);var extremes=packed.extremes;var saveAxes=this.axes_;this.computeYAxisRanges_(extremes);var newAxes=this.axes_;this.axes_ = saveAxes;return newAxes.map(function(axis){return axis.extremeRange;});}; /** - * Returns the currently-visible y-range for an axis. This can be affected by - * zooming, panning or a call to updateOptions. Axis indices are zero-based. If - * called with no arguments, returns the range of the first axis. - * Returns a two-element array: [bottom, top]. - */Dygraph.prototype.yAxisRange = function(idx){if(typeof idx == "undefined")idx = 0;if(idx < 0 || idx >= this.axes_.length){return null;}var axis=this.axes_[idx];return [axis.computedValueRange[0],axis.computedValueRange[1]];}; /** - * Returns the currently-visible y-ranges for each axis. This can be affected by - * zooming, panning, calls to updateOptions, etc. - * Returns an array of [bottom, top] pairs, one for each y-axis. - */Dygraph.prototype.yAxisRanges = function(){var ret=[];for(var i=0;i < this.axes_.length;i++) {ret.push(this.yAxisRange(i));}return ret;}; // TODO(danvk): use these functions throughout dygraphs. -/** - * Convert from data coordinates to canvas/div X/Y coordinates. - * If specified, do this conversion for the coordinate system of a particular - * axis. Uses the first axis by default. - * Returns a two-element array: [X, Y] - * - * Note: use toDomXCoord instead of toDomCoords(x, null) and use toDomYCoord - * instead of toDomCoords(null, y, axis). - */Dygraph.prototype.toDomCoords = function(x,y,axis){return [this.toDomXCoord(x),this.toDomYCoord(y,axis)];}; /** - * Convert from data x coordinates to canvas/div X coordinate. - * If specified, do this conversion for the coordinate system of a particular - * axis. - * Returns a single value or null if x is null. - */Dygraph.prototype.toDomXCoord = function(x){if(x === null){return null;}var area=this.plotter_.area;var xRange=this.xAxisRange();return area.x + (x - xRange[0]) / (xRange[1] - xRange[0]) * area.w;}; /** - * Convert from data x coordinates to canvas/div Y coordinate and optional - * axis. Uses the first axis by default. - * - * returns a single value or null if y is null. - */Dygraph.prototype.toDomYCoord = function(y,axis){var pct=this.toPercentYCoord(y,axis);if(pct === null){return null;}var area=this.plotter_.area;return area.y + pct * area.h;}; /** - * Convert from canvas/div coords to data coordinates. - * If specified, do this conversion for the coordinate system of a particular - * axis. Uses the first axis by default. - * Returns a two-element array: [X, Y]. - * - * Note: use toDataXCoord instead of toDataCoords(x, null) and use toDataYCoord - * instead of toDataCoords(null, y, axis). - */Dygraph.prototype.toDataCoords = function(x,y,axis){return [this.toDataXCoord(x),this.toDataYCoord(y,axis)];}; /** - * Convert from canvas/div x coordinate to data coordinate. - * - * If x is null, this returns null. - */Dygraph.prototype.toDataXCoord = function(x){if(x === null){return null;}var area=this.plotter_.area;var xRange=this.xAxisRange();if(!this.attributes_.getForAxis("logscale",'x')){return xRange[0] + (x - area.x) / area.w * (xRange[1] - xRange[0]);}else {var pct=(x - area.x) / area.w;return utils.logRangeFraction(xRange[0],xRange[1],pct);}}; /** - * Convert from canvas/div y coord to value. - * - * If y is null, this returns null. - * if axis is null, this uses the first axis. - */Dygraph.prototype.toDataYCoord = function(y,axis){if(y === null){return null;}var area=this.plotter_.area;var yRange=this.yAxisRange(axis);if(typeof axis == "undefined")axis = 0;if(!this.attributes_.getForAxis("logscale",axis)){return yRange[0] + (area.y + area.h - y) / area.h * (yRange[1] - yRange[0]);}else { // Computing the inverse of toDomCoord. -var pct=(y - area.y) / area.h; // Note reversed yRange, y1 is on top with pct==0. -return utils.logRangeFraction(yRange[1],yRange[0],pct);}}; /** - * Converts a y for an axis to a percentage from the top to the - * bottom of the drawing area. - * - * If the coordinate represents a value visible on the canvas, then - * the value will be between 0 and 1, where 0 is the top of the canvas. - * However, this method will return values outside the range, as - * values can fall outside the canvas. - * - * If y is null, this returns null. - * if axis is null, this uses the first axis. - * - * @param {number} y The data y-coordinate. - * @param {number} [axis] The axis number on which the data coordinate lives. - * @return {number} A fraction in [0, 1] where 0 = the top edge. - */Dygraph.prototype.toPercentYCoord = function(y,axis){if(y === null){return null;}if(typeof axis == "undefined")axis = 0;var yRange=this.yAxisRange(axis);var pct;var logscale=this.attributes_.getForAxis("logscale",axis);if(logscale){var logr0=utils.log10(yRange[0]);var logr1=utils.log10(yRange[1]);pct = (logr1 - utils.log10(y)) / (logr1 - logr0);}else { // yRange[1] - y is unit distance from the bottom. -// yRange[1] - yRange[0] is the scale of the range. -// (yRange[1] - y) / (yRange[1] - yRange[0]) is the % from the bottom. -pct = (yRange[1] - y) / (yRange[1] - yRange[0]);}return pct;}; /** - * Converts an x value to a percentage from the left to the right of - * the drawing area. - * - * If the coordinate represents a value visible on the canvas, then - * the value will be between 0 and 1, where 0 is the left of the canvas. - * However, this method will return values outside the range, as - * values can fall outside the canvas. - * - * If x is null, this returns null. - * @param {number} x The data x-coordinate. - * @return {number} A fraction in [0, 1] where 0 = the left edge. - */Dygraph.prototype.toPercentXCoord = function(x){if(x === null){return null;}var xRange=this.xAxisRange();var pct;var logscale=this.attributes_.getForAxis("logscale",'x');if(logscale === true){ // logscale can be null so we test for true explicitly. -var logr0=utils.log10(xRange[0]);var logr1=utils.log10(xRange[1]);pct = (utils.log10(x) - logr0) / (logr1 - logr0);}else { // x - xRange[0] is unit distance from the left. -// xRange[1] - xRange[0] is the scale of the range. -// The full expression below is the % from the left. -pct = (x - xRange[0]) / (xRange[1] - xRange[0]);}return pct;}; /** - * Returns the number of columns (including the independent variable). - * @return {number} The number of columns. - */Dygraph.prototype.numColumns = function(){if(!this.rawData_)return 0;return this.rawData_[0]?this.rawData_[0].length:this.attr_("labels").length;}; /** - * Returns the number of rows (excluding any header/label row). - * @return {number} The number of rows, less any header. - */Dygraph.prototype.numRows = function(){if(!this.rawData_)return 0;return this.rawData_.length;}; /** - * Returns the value in the given row and column. If the row and column exceed - * the bounds on the data, returns null. Also returns null if the value is - * missing. - * @param {number} row The row number of the data (0-based). Row 0 is the - * first row of data, not a header row. - * @param {number} col The column number of the data (0-based) - * @return {number} The value in the specified cell or null if the row/col - * were out of range. - */Dygraph.prototype.getValue = function(row,col){if(row < 0 || row > this.rawData_.length)return null;if(col < 0 || col > this.rawData_[row].length)return null;return this.rawData_[row][col];}; /** - * Generates interface elements for the Dygraph: a containing div, a div to - * display the current point, and a textbox to adjust the rolling average - * period. Also creates the Renderer/Layout elements. - * @private - */Dygraph.prototype.createInterface_ = function(){ // Create the all-enclosing graph div -var enclosing=this.maindiv_;this.graphDiv = document.createElement("div"); // TODO(danvk): any other styles that are useful to set here? -this.graphDiv.style.textAlign = 'left'; // This is a CSS "reset" -this.graphDiv.style.position = 'relative';enclosing.appendChild(this.graphDiv); // Create the canvas for interactive parts of the chart. -this.canvas_ = utils.createCanvas();this.canvas_.style.position = "absolute"; // ... and for static parts of the chart. -this.hidden_ = this.createPlotKitCanvas_(this.canvas_);this.canvas_ctx_ = utils.getContext(this.canvas_);this.hidden_ctx_ = utils.getContext(this.hidden_);this.resizeElements_(); // The interactive parts of the graph are drawn on top of the chart. -this.graphDiv.appendChild(this.hidden_);this.graphDiv.appendChild(this.canvas_);this.mouseEventElement_ = this.createMouseEventElement_(); // Create the grapher -this.layout_ = new _dygraphLayout2['default'](this);var dygraph=this;this.mouseMoveHandler_ = function(e){dygraph.mouseMove_(e);};this.mouseOutHandler_ = function(e){ // The mouse has left the chart if: -// 1. e.target is inside the chart -// 2. e.relatedTarget is outside the chart -var target=e.target || e.fromElement;var relatedTarget=e.relatedTarget || e.toElement;if(utils.isNodeContainedBy(target,dygraph.graphDiv) && !utils.isNodeContainedBy(relatedTarget,dygraph.graphDiv)){dygraph.mouseOut_(e);}};this.addAndTrackEvent(window,'mouseout',this.mouseOutHandler_);this.addAndTrackEvent(this.mouseEventElement_,'mousemove',this.mouseMoveHandler_); // Don't recreate and register the resize handler on subsequent calls. -// This happens when the graph is resized. -if(!this.resizeHandler_){this.resizeHandler_ = function(e){dygraph.resize();}; // Update when the window is resized. -// TODO(danvk): drop frames depending on complexity of the chart. -this.addAndTrackEvent(window,'resize',this.resizeHandler_);}};Dygraph.prototype.resizeElements_ = function(){this.graphDiv.style.width = this.width_ + "px";this.graphDiv.style.height = this.height_ + "px";var pixelRatioOption=this.getNumericOption('pixelRatio');var canvasScale=pixelRatioOption || utils.getContextPixelRatio(this.canvas_ctx_);this.canvas_.width = this.width_ * canvasScale;this.canvas_.height = this.height_ * canvasScale;this.canvas_.style.width = this.width_ + "px"; // for IE -this.canvas_.style.height = this.height_ + "px"; // for IE -if(canvasScale !== 1){this.canvas_ctx_.scale(canvasScale,canvasScale);}var hiddenScale=pixelRatioOption || utils.getContextPixelRatio(this.hidden_ctx_);this.hidden_.width = this.width_ * hiddenScale;this.hidden_.height = this.height_ * hiddenScale;this.hidden_.style.width = this.width_ + "px"; // for IE -this.hidden_.style.height = this.height_ + "px"; // for IE -if(hiddenScale !== 1){this.hidden_ctx_.scale(hiddenScale,hiddenScale);}}; /** - * Detach DOM elements in the dygraph and null out all data references. - * Calling this when you're done with a dygraph can dramatically reduce memory - * usage. See, e.g., the tests/perf.html example. - */Dygraph.prototype.destroy = function(){this.canvas_ctx_.restore();this.hidden_ctx_.restore(); // Destroy any plugins, in the reverse order that they were registered. -for(var i=this.plugins_.length - 1;i >= 0;i--) {var p=this.plugins_.pop();if(p.plugin.destroy)p.plugin.destroy();}var removeRecursive=function removeRecursive(node){while(node.hasChildNodes()) {removeRecursive(node.firstChild);node.removeChild(node.firstChild);}};this.removeTrackedEvents_(); // remove mouse event handlers (This may not be necessary anymore) -utils.removeEvent(window,'mouseout',this.mouseOutHandler_);utils.removeEvent(this.mouseEventElement_,'mousemove',this.mouseMoveHandler_); // remove window handlers -utils.removeEvent(window,'resize',this.resizeHandler_);this.resizeHandler_ = null;removeRecursive(this.maindiv_);var nullOut=function nullOut(obj){for(var n in obj) {if(typeof obj[n] === 'object'){obj[n] = null;}}}; // These may not all be necessary, but it can't hurt... -nullOut(this.layout_);nullOut(this.plotter_);nullOut(this);}; /** - * Creates the canvas on which the chart will be drawn. Only the Renderer ever - * draws on this particular canvas. All Dygraph work (i.e. drawing hover dots - * or the zoom rectangles) is done on this.canvas_. - * @param {Object} canvas The Dygraph canvas over which to overlay the plot - * @return {Object} The newly-created canvas - * @private - */Dygraph.prototype.createPlotKitCanvas_ = function(canvas){var h=utils.createCanvas();h.style.position = "absolute"; // TODO(danvk): h should be offset from canvas. canvas needs to include -// some extra area to make it easier to zoom in on the far left and far -// right. h needs to be precisely the plot area, so that clipping occurs. -h.style.top = canvas.style.top;h.style.left = canvas.style.left;h.width = this.width_;h.height = this.height_;h.style.width = this.width_ + "px"; // for IE -h.style.height = this.height_ + "px"; // for IE -return h;}; /** - * Creates an overlay element used to handle mouse events. - * @return {Object} The mouse event element. - * @private - */Dygraph.prototype.createMouseEventElement_ = function(){return this.canvas_;}; /** - * Generate a set of distinct colors for the data series. This is done with a - * color wheel. Saturation/Value are customizable, and the hue is - * equally-spaced around the color wheel. If a custom set of colors is - * specified, that is used instead. - * @private - */Dygraph.prototype.setColors_ = function(){var labels=this.getLabels();var num=labels.length - 1;this.colors_ = [];this.colorsMap_ = {}; // These are used for when no custom colors are specified. -var sat=this.getNumericOption('colorSaturation') || 1.0;var val=this.getNumericOption('colorValue') || 0.5;var half=Math.ceil(num / 2);var colors=this.getOption('colors');var visibility=this.visibility();for(var i=0;i < num;i++) {if(!visibility[i]){continue;}var label=labels[i + 1];var colorStr=this.attributes_.getForSeries('color',label);if(!colorStr){if(colors){colorStr = colors[i % colors.length];}else { // alternate colors for high contrast. -var idx=i % 2?half + (i + 1) / 2:Math.ceil((i + 1) / 2);var hue=1.0 * idx / (1 + num);colorStr = utils.hsvToRGB(hue,sat,val);}}this.colors_.push(colorStr);this.colorsMap_[label] = colorStr;}}; /** - * Return the list of colors. This is either the list of colors passed in the - * attributes or the autogenerated list of rgb(r,g,b) strings. - * This does not return colors for invisible series. - * @return {Array.} The list of colors. - */Dygraph.prototype.getColors = function(){return this.colors_;}; /** - * Returns a few attributes of a series, i.e. its color, its visibility, which - * axis it's assigned to, and its column in the original data. - * Returns null if the series does not exist. - * Otherwise, returns an object with column, visibility, color and axis properties. - * The "axis" property will be set to 1 for y1 and 2 for y2. - * The "column" property can be fed back into getValue(row, column) to get - * values for this series. - */Dygraph.prototype.getPropertiesForSeries = function(series_name){var idx=-1;var labels=this.getLabels();for(var i=1;i < labels.length;i++) {if(labels[i] == series_name){idx = i;break;}}if(idx == -1)return null;return {name:series_name,column:idx,visible:this.visibility()[idx - 1],color:this.colorsMap_[series_name],axis:1 + this.attributes_.axisForSeries(series_name)};}; /** - * Create the text box to adjust the averaging period - * @private - */Dygraph.prototype.createRollInterface_ = function(){var _this=this; // Create a roller if one doesn't exist already. -var roller=this.roller_;if(!roller){this.roller_ = roller = document.createElement("input");roller.type = "text";roller.style.display = "none";roller.className = 'dygraph-roller';this.graphDiv.appendChild(roller);}var display=this.getBooleanOption('showRoller')?'block':'none';var area=this.getArea();var textAttr={"top":area.y + area.h - 25 + "px","left":area.x + 1 + "px","display":display};roller.size = "2";roller.value = this.rollPeriod_;utils.update(roller.style,textAttr);roller.onchange = function(){return _this.adjustRoll(roller.value);};}; /** - * Set up all the mouse handlers needed to capture dragging behavior for zoom - * events. - * @private - */Dygraph.prototype.createDragInterface_ = function(){var context={ // Tracks whether the mouse is down right now -isZooming:false,isPanning:false, // is this drag part of a pan? -is2DPan:false, // if so, is that pan 1- or 2-dimensional? -dragStartX:null, // pixel coordinates -dragStartY:null, // pixel coordinates -dragEndX:null, // pixel coordinates -dragEndY:null, // pixel coordinates -dragDirection:null,prevEndX:null, // pixel coordinates -prevEndY:null, // pixel coordinates -prevDragDirection:null,cancelNextDblclick:false, // see comment in dygraph-interaction-model.js -// The value on the left side of the graph when a pan operation starts. -initialLeftmostDate:null, // The number of units each pixel spans. (This won't be valid for log -// scales) -xUnitsPerPixel:null, // TODO(danvk): update this comment -// The range in second/value units that the viewport encompasses during a -// panning operation. -dateRange:null, // Top-left corner of the canvas, in DOM coords -// TODO(konigsberg): Rename topLeftCanvasX, topLeftCanvasY. -px:0,py:0, // Values for use with panEdgeFraction, which limit how far outside the -// graph's data boundaries it can be panned. -boundedDates:null, // [minDate, maxDate] -boundedValues:null, // [[minValue, maxValue] ...] -// We cover iframes during mouse interactions. See comments in -// dygraph-utils.js for more info on why this is a good idea. -tarp:new _iframeTarp2['default'](), // contextB is the same thing as this context object but renamed. -initializeMouseDown:function initializeMouseDown(event,g,contextB){ // prevents mouse drags from selecting page text. -if(event.preventDefault){event.preventDefault(); // Firefox, Chrome, etc. -}else {event.returnValue = false; // IE -event.cancelBubble = true;}var canvasPos=utils.findPos(g.canvas_);contextB.px = canvasPos.x;contextB.py = canvasPos.y;contextB.dragStartX = utils.dragGetX_(event,contextB);contextB.dragStartY = utils.dragGetY_(event,contextB);contextB.cancelNextDblclick = false;contextB.tarp.cover();},destroy:function destroy(){var context=this;if(context.isZooming || context.isPanning){context.isZooming = false;context.dragStartX = null;context.dragStartY = null;}if(context.isPanning){context.isPanning = false;context.draggingDate = null;context.dateRange = null;for(var i=0;i < self.axes_.length;i++) {delete self.axes_[i].draggingValue;delete self.axes_[i].dragValueRange;}}context.tarp.uncover();}};var interactionModel=this.getOption("interactionModel"); // Self is the graph. -var self=this; // Function that binds the graph and context to the handler. -var bindHandler=function bindHandler(handler){return function(event){handler(event,self,context);};};for(var eventName in interactionModel) {if(!interactionModel.hasOwnProperty(eventName))continue;this.addAndTrackEvent(this.mouseEventElement_,eventName,bindHandler(interactionModel[eventName]));} // If the user releases the mouse button during a drag, but not over the -// canvas, then it doesn't count as a zooming action. -if(!interactionModel.willDestroyContextMyself){var mouseUpHandler=function mouseUpHandler(event){context.destroy();};this.addAndTrackEvent(document,'mouseup',mouseUpHandler);}}; /** - * Draw a gray zoom rectangle over the desired area of the canvas. Also clears - * up any previous zoom rectangles that were drawn. This could be optimized to - * avoid extra redrawing, but it's tricky to avoid interactions with the status - * dots. - * - * @param {number} direction the direction of the zoom rectangle. Acceptable - * values are utils.HORIZONTAL and utils.VERTICAL. - * @param {number} startX The X position where the drag started, in canvas - * coordinates. - * @param {number} endX The current X position of the drag, in canvas coords. - * @param {number} startY The Y position where the drag started, in canvas - * coordinates. - * @param {number} endY The current Y position of the drag, in canvas coords. - * @param {number} prevDirection the value of direction on the previous call to - * this function. Used to avoid excess redrawing - * @param {number} prevEndX The value of endX on the previous call to this - * function. Used to avoid excess redrawing - * @param {number} prevEndY The value of endY on the previous call to this - * function. Used to avoid excess redrawing - * @private - */Dygraph.prototype.drawZoomRect_ = function(direction,startX,endX,startY,endY,prevDirection,prevEndX,prevEndY){var ctx=this.canvas_ctx_; // Clean up from the previous rect if necessary -if(prevDirection == utils.HORIZONTAL){ctx.clearRect(Math.min(startX,prevEndX),this.layout_.getPlotArea().y,Math.abs(startX - prevEndX),this.layout_.getPlotArea().h);}else if(prevDirection == utils.VERTICAL){ctx.clearRect(this.layout_.getPlotArea().x,Math.min(startY,prevEndY),this.layout_.getPlotArea().w,Math.abs(startY - prevEndY));} // Draw a light-grey rectangle to show the new viewing area -if(direction == utils.HORIZONTAL){if(endX && startX){ctx.fillStyle = "rgba(128,128,128,0.33)";ctx.fillRect(Math.min(startX,endX),this.layout_.getPlotArea().y,Math.abs(endX - startX),this.layout_.getPlotArea().h);}}else if(direction == utils.VERTICAL){if(endY && startY){ctx.fillStyle = "rgba(128,128,128,0.33)";ctx.fillRect(this.layout_.getPlotArea().x,Math.min(startY,endY),this.layout_.getPlotArea().w,Math.abs(endY - startY));}}}; /** - * Clear the zoom rectangle (and perform no zoom). - * @private - */Dygraph.prototype.clearZoomRect_ = function(){this.currentZoomRectArgs_ = null;this.canvas_ctx_.clearRect(0,0,this.width_,this.height_);}; /** - * Zoom to something containing [lowX, highX]. These are pixel coordinates in - * the canvas. The exact zoom window may be slightly larger if there are no data - * points near lowX or highX. Don't confuse this function with doZoomXDates, - * which accepts dates that match the raw data. This function redraws the graph. - * - * @param {number} lowX The leftmost pixel value that should be visible. - * @param {number} highX The rightmost pixel value that should be visible. - * @private - */Dygraph.prototype.doZoomX_ = function(lowX,highX){this.currentZoomRectArgs_ = null; // Find the earliest and latest dates contained in this canvasx range. -// Convert the call to date ranges of the raw data. -var minDate=this.toDataXCoord(lowX);var maxDate=this.toDataXCoord(highX);this.doZoomXDates_(minDate,maxDate);}; /** - * Zoom to something containing [minDate, maxDate] values. Don't confuse this - * method with doZoomX which accepts pixel coordinates. This function redraws - * the graph. - * - * @param {number} minDate The minimum date that should be visible. - * @param {number} maxDate The maximum date that should be visible. - * @private - */Dygraph.prototype.doZoomXDates_ = function(minDate,maxDate){var _this2=this; // TODO(danvk): when xAxisRange is null (i.e. "fit to data", the animation -// can produce strange effects. Rather than the x-axis transitioning slowly -// between values, it can jerk around.) -var old_window=this.xAxisRange();var new_window=[minDate,maxDate];var zoomCallback=this.getFunctionOption('zoomCallback');this.doAnimatedZoom(old_window,new_window,null,null,function(){if(zoomCallback){zoomCallback.call(_this2,minDate,maxDate,_this2.yAxisRanges());}});}; /** - * Zoom to something containing [lowY, highY]. These are pixel coordinates in - * the canvas. This function redraws the graph. - * - * @param {number} lowY The topmost pixel value that should be visible. - * @param {number} highY The lowest pixel value that should be visible. - * @private - */Dygraph.prototype.doZoomY_ = function(lowY,highY){var _this3=this;this.currentZoomRectArgs_ = null; // Find the highest and lowest values in pixel range for each axis. -// Note that lowY (in pixels) corresponds to the max Value (in data coords). -// This is because pixels increase as you go down on the screen, whereas data -// coordinates increase as you go up the screen. -var oldValueRanges=this.yAxisRanges();var newValueRanges=[];for(var i=0;i < this.axes_.length;i++) {var hi=this.toDataYCoord(lowY,i);var low=this.toDataYCoord(highY,i);newValueRanges.push([low,hi]);}var zoomCallback=this.getFunctionOption('zoomCallback');this.doAnimatedZoom(null,null,oldValueRanges,newValueRanges,function(){if(zoomCallback){var _xAxisRange=_this3.xAxisRange();var _xAxisRange2=_slicedToArray(_xAxisRange,2);var minX=_xAxisRange2[0];var maxX=_xAxisRange2[1];zoomCallback.call(_this3,minX,maxX,_this3.yAxisRanges());}});}; /** - * Transition function to use in animations. Returns values between 0.0 - * (totally old values) and 1.0 (totally new values) for each frame. - * @private - */Dygraph.zoomAnimationFunction = function(frame,numFrames){var k=1.5;return (1.0 - Math.pow(k,-frame)) / (1.0 - Math.pow(k,-numFrames));}; /** - * Reset the zoom to the original view coordinates. This is the same as - * double-clicking on the graph. - */Dygraph.prototype.resetZoom = function(){var _this4=this;var dirtyX=this.isZoomed('x');var dirtyY=this.isZoomed('y');var dirty=dirtyX || dirtyY; // Clear any selection, since it's likely to be drawn in the wrong place. -this.clearSelection();if(!dirty)return; // Calculate extremes to avoid lack of padding on reset. -var _xAxisExtremes=this.xAxisExtremes();var _xAxisExtremes2=_slicedToArray(_xAxisExtremes,2);var minDate=_xAxisExtremes2[0];var maxDate=_xAxisExtremes2[1];var animatedZooms=this.getBooleanOption('animatedZooms');var zoomCallback=this.getFunctionOption('zoomCallback'); // TODO(danvk): merge this block w/ the code below. -// TODO(danvk): factor out a generic, public zoomTo method. -if(!animatedZooms){this.dateWindow_ = null;this.axes_.forEach(function(axis){if(axis.valueRange)delete axis.valueRange;});this.drawGraph_();if(zoomCallback){zoomCallback.call(this,minDate,maxDate,this.yAxisRanges());}return;}var oldWindow=null,newWindow=null,oldValueRanges=null,newValueRanges=null;if(dirtyX){oldWindow = this.xAxisRange();newWindow = [minDate,maxDate];}if(dirtyY){oldValueRanges = this.yAxisRanges();newValueRanges = this.yAxisExtremes();}this.doAnimatedZoom(oldWindow,newWindow,oldValueRanges,newValueRanges,function(){_this4.dateWindow_ = null;_this4.axes_.forEach(function(axis){if(axis.valueRange)delete axis.valueRange;});if(zoomCallback){zoomCallback.call(_this4,minDate,maxDate,_this4.yAxisRanges());}});}; /** - * Combined animation logic for all zoom functions. - * either the x parameters or y parameters may be null. - * @private - */Dygraph.prototype.doAnimatedZoom = function(oldXRange,newXRange,oldYRanges,newYRanges,callback){var _this5=this;var steps=this.getBooleanOption("animatedZooms")?Dygraph.ANIMATION_STEPS:1;var windows=[];var valueRanges=[];var step,frac;if(oldXRange !== null && newXRange !== null){for(step = 1;step <= steps;step++) {frac = Dygraph.zoomAnimationFunction(step,steps);windows[step - 1] = [oldXRange[0] * (1 - frac) + frac * newXRange[0],oldXRange[1] * (1 - frac) + frac * newXRange[1]];}}if(oldYRanges !== null && newYRanges !== null){for(step = 1;step <= steps;step++) {frac = Dygraph.zoomAnimationFunction(step,steps);var thisRange=[];for(var j=0;j < this.axes_.length;j++) {thisRange.push([oldYRanges[j][0] * (1 - frac) + frac * newYRanges[j][0],oldYRanges[j][1] * (1 - frac) + frac * newYRanges[j][1]]);}valueRanges[step - 1] = thisRange;}}utils.repeatAndCleanup(function(step){if(valueRanges.length){for(var i=0;i < _this5.axes_.length;i++) {var w=valueRanges[step][i];_this5.axes_[i].valueRange = [w[0],w[1]];}}if(windows.length){_this5.dateWindow_ = windows[step];}_this5.drawGraph_();},steps,Dygraph.ANIMATION_DURATION / steps,callback);}; /** - * Get the current graph's area object. - * - * Returns: {x, y, w, h} - */Dygraph.prototype.getArea = function(){return this.plotter_.area;}; /** - * Convert a mouse event to DOM coordinates relative to the graph origin. - * - * Returns a two-element array: [X, Y]. - */Dygraph.prototype.eventToDomCoords = function(event){if(event.offsetX && event.offsetY){return [event.offsetX,event.offsetY];}else {var eventElementPos=utils.findPos(this.mouseEventElement_);var canvasx=utils.pageX(event) - eventElementPos.x;var canvasy=utils.pageY(event) - eventElementPos.y;return [canvasx,canvasy];}}; /** - * Given a canvas X coordinate, find the closest row. - * @param {number} domX graph-relative DOM X coordinate - * Returns {number} row number. - * @private - */Dygraph.prototype.findClosestRow = function(domX){var minDistX=Infinity;var closestRow=-1;var sets=this.layout_.points;for(var i=0;i < sets.length;i++) {var points=sets[i];var len=points.length;for(var j=0;j < len;j++) {var point=points[j];if(!utils.isValidPoint(point,true))continue;var dist=Math.abs(point.canvasx - domX);if(dist < minDistX){minDistX = dist;closestRow = point.idx;}}}return closestRow;}; /** - * Given canvas X,Y coordinates, find the closest point. - * - * This finds the individual data point across all visible series - * that's closest to the supplied DOM coordinates using the standard - * Euclidean X,Y distance. - * - * @param {number} domX graph-relative DOM X coordinate - * @param {number} domY graph-relative DOM Y coordinate - * Returns: {row, seriesName, point} - * @private - */Dygraph.prototype.findClosestPoint = function(domX,domY){var minDist=Infinity;var dist,dx,dy,point,closestPoint,closestSeries,closestRow;for(var setIdx=this.layout_.points.length - 1;setIdx >= 0;--setIdx) {var points=this.layout_.points[setIdx];for(var i=0;i < points.length;++i) {point = points[i];if(!utils.isValidPoint(point))continue;dx = point.canvasx - domX;dy = point.canvasy - domY;dist = dx * dx + dy * dy;if(dist < minDist){minDist = dist;closestPoint = point;closestSeries = setIdx;closestRow = point.idx;}}}var name=this.layout_.setNames[closestSeries];return {row:closestRow,seriesName:name,point:closestPoint};}; /** - * Given canvas X,Y coordinates, find the touched area in a stacked graph. - * - * This first finds the X data point closest to the supplied DOM X coordinate, - * then finds the series which puts the Y coordinate on top of its filled area, - * using linear interpolation between adjacent point pairs. - * - * @param {number} domX graph-relative DOM X coordinate - * @param {number} domY graph-relative DOM Y coordinate - * Returns: {row, seriesName, point} - * @private - */Dygraph.prototype.findStackedPoint = function(domX,domY){var row=this.findClosestRow(domX);var closestPoint,closestSeries;for(var setIdx=0;setIdx < this.layout_.points.length;++setIdx) {var boundary=this.getLeftBoundary_(setIdx);var rowIdx=row - boundary;var points=this.layout_.points[setIdx];if(rowIdx >= points.length)continue;var p1=points[rowIdx];if(!utils.isValidPoint(p1))continue;var py=p1.canvasy;if(domX > p1.canvasx && rowIdx + 1 < points.length){ // interpolate series Y value using next point -var p2=points[rowIdx + 1];if(utils.isValidPoint(p2)){var dx=p2.canvasx - p1.canvasx;if(dx > 0){var r=(domX - p1.canvasx) / dx;py += r * (p2.canvasy - p1.canvasy);}}}else if(domX < p1.canvasx && rowIdx > 0){ // interpolate series Y value using previous point -var p0=points[rowIdx - 1];if(utils.isValidPoint(p0)){var dx=p1.canvasx - p0.canvasx;if(dx > 0){var r=(p1.canvasx - domX) / dx;py += r * (p0.canvasy - p1.canvasy);}}} // Stop if the point (domX, py) is above this series' upper edge -if(setIdx === 0 || py < domY){closestPoint = p1;closestSeries = setIdx;}}var name=this.layout_.setNames[closestSeries];return {row:row,seriesName:name,point:closestPoint};}; /** - * When the mouse moves in the canvas, display information about a nearby data - * point and draw dots over those points in the data series. This function - * takes care of cleanup of previously-drawn dots. - * @param {Object} event The mousemove event from the browser. - * @private - */Dygraph.prototype.mouseMove_ = function(event){ // This prevents JS errors when mousing over the canvas before data loads. -var points=this.layout_.points;if(points === undefined || points === null)return;var canvasCoords=this.eventToDomCoords(event);var canvasx=canvasCoords[0];var canvasy=canvasCoords[1];var highlightSeriesOpts=this.getOption("highlightSeriesOpts");var selectionChanged=false;if(highlightSeriesOpts && !this.isSeriesLocked()){var closest;if(this.getBooleanOption("stackedGraph")){closest = this.findStackedPoint(canvasx,canvasy);}else {closest = this.findClosestPoint(canvasx,canvasy);}selectionChanged = this.setSelection(closest.row,closest.seriesName);}else {var idx=this.findClosestRow(canvasx);selectionChanged = this.setSelection(idx);}var callback=this.getFunctionOption("highlightCallback");if(callback && selectionChanged){callback.call(this,event,this.lastx_,this.selPoints_,this.lastRow_,this.highlightSet_);}}; /** - * Fetch left offset from the specified set index or if not passed, the - * first defined boundaryIds record (see bug #236). - * @private - */Dygraph.prototype.getLeftBoundary_ = function(setIdx){if(this.boundaryIds_[setIdx]){return this.boundaryIds_[setIdx][0];}else {for(var i=0;i < this.boundaryIds_.length;i++) {if(this.boundaryIds_[i] !== undefined){return this.boundaryIds_[i][0];}}return 0;}};Dygraph.prototype.animateSelection_ = function(direction){var totalSteps=10;var millis=30;if(this.fadeLevel === undefined)this.fadeLevel = 0;if(this.animateId === undefined)this.animateId = 0;var start=this.fadeLevel;var steps=direction < 0?start:totalSteps - start;if(steps <= 0){if(this.fadeLevel){this.updateSelection_(1.0);}return;}var thisId=++this.animateId;var that=this;var cleanupIfClearing=function cleanupIfClearing(){ // if we haven't reached fadeLevel 0 in the max frame time, -// ensure that the clear happens and just go to 0 -if(that.fadeLevel !== 0 && direction < 0){that.fadeLevel = 0;that.clearSelection();}};utils.repeatAndCleanup(function(n){ // ignore simultaneous animations -if(that.animateId != thisId)return;that.fadeLevel += direction;if(that.fadeLevel === 0){that.clearSelection();}else {that.updateSelection_(that.fadeLevel / totalSteps);}},steps,millis,cleanupIfClearing);}; /** - * Draw dots over the selectied points in the data series. This function - * takes care of cleanup of previously-drawn dots. - * @private - */Dygraph.prototype.updateSelection_ = function(opt_animFraction){ /*var defaultPrevented = */this.cascadeEvents_('select',{selectedRow:this.lastRow_ === -1?undefined:this.lastRow_,selectedX:this.lastx_ === -1?undefined:this.lastx_,selectedPoints:this.selPoints_}); // TODO(danvk): use defaultPrevented here? -// Clear the previously drawn vertical, if there is one -var i;var ctx=this.canvas_ctx_;if(this.getOption('highlightSeriesOpts')){ctx.clearRect(0,0,this.width_,this.height_);var alpha=1.0 - this.getNumericOption('highlightSeriesBackgroundAlpha');var backgroundColor=utils.toRGB_(this.getOption('highlightSeriesBackgroundColor'));if(alpha){ // Activating background fade includes an animation effect for a gradual -// fade. TODO(klausw): make this independently configurable if it causes -// issues? Use a shared preference to control animations? -var animateBackgroundFade=true;if(animateBackgroundFade){if(opt_animFraction === undefined){ // start a new animation -this.animateSelection_(1);return;}alpha *= opt_animFraction;}ctx.fillStyle = 'rgba(' + backgroundColor.r + ',' + backgroundColor.g + ',' + backgroundColor.b + ',' + alpha + ')';ctx.fillRect(0,0,this.width_,this.height_);} // Redraw only the highlighted series in the interactive canvas (not the -// static plot canvas, which is where series are usually drawn). -this.plotter_._renderLineChart(this.highlightSet_,ctx);}else if(this.previousVerticalX_ >= 0){ // Determine the maximum highlight circle size. -var maxCircleSize=0;var labels=this.attr_('labels');for(i = 1;i < labels.length;i++) {var r=this.getNumericOption('highlightCircleSize',labels[i]);if(r > maxCircleSize)maxCircleSize = r;}var px=this.previousVerticalX_;ctx.clearRect(px - maxCircleSize - 1,0,2 * maxCircleSize + 2,this.height_);}if(this.selPoints_.length > 0){ // Draw colored circles over the center of each selected point -var canvasx=this.selPoints_[0].canvasx;ctx.save();for(i = 0;i < this.selPoints_.length;i++) {var pt=this.selPoints_[i];if(isNaN(pt.canvasy))continue;var circleSize=this.getNumericOption('highlightCircleSize',pt.name);var callback=this.getFunctionOption("drawHighlightPointCallback",pt.name);var color=this.plotter_.colors[pt.name];if(!callback){callback = utils.Circles.DEFAULT;}ctx.lineWidth = this.getNumericOption('strokeWidth',pt.name);ctx.strokeStyle = color;ctx.fillStyle = color;callback.call(this,this,pt.name,ctx,canvasx,pt.canvasy,color,circleSize,pt.idx);}ctx.restore();this.previousVerticalX_ = canvasx;}}; /** - * Manually set the selected points and display information about them in the - * legend. The selection can be cleared using clearSelection() and queried - * using getSelection(). - * - * To set a selected series but not a selected point, call setSelection with - * row=false and the selected series name. - * - * @param {number} row Row number that should be highlighted (i.e. appear with - * hover dots on the chart). - * @param {seriesName} optional series name to highlight that series with the - * the highlightSeriesOpts setting. - * @param { locked } optional If true, keep seriesName selected when mousing - * over the graph, disabling closest-series highlighting. Call clearSelection() - * to unlock it. - */Dygraph.prototype.setSelection = function(row,opt_seriesName,opt_locked){ // Extract the points we've selected -this.selPoints_ = [];var changed=false;if(row !== false && row >= 0){if(row != this.lastRow_)changed = true;this.lastRow_ = row;for(var setIdx=0;setIdx < this.layout_.points.length;++setIdx) {var points=this.layout_.points[setIdx]; // Check if the point at the appropriate index is the point we're looking -// for. If it is, just use it, otherwise search the array for a point -// in the proper place. -var setRow=row - this.getLeftBoundary_(setIdx);if(setRow >= 0 && setRow < points.length && points[setRow].idx == row){var point=points[setRow];if(point.yval !== null)this.selPoints_.push(point);}else {for(var pointIdx=0;pointIdx < points.length;++pointIdx) {var point=points[pointIdx];if(point.idx == row){if(point.yval !== null){this.selPoints_.push(point);}break;}}}}}else {if(this.lastRow_ >= 0)changed = true;this.lastRow_ = -1;}if(this.selPoints_.length){this.lastx_ = this.selPoints_[0].xval;}else {this.lastx_ = -1;}if(opt_seriesName !== undefined){if(this.highlightSet_ !== opt_seriesName)changed = true;this.highlightSet_ = opt_seriesName;}if(opt_locked !== undefined){this.lockedSet_ = opt_locked;}if(changed){this.updateSelection_(undefined);}return changed;}; /** - * The mouse has left the canvas. Clear out whatever artifacts remain - * @param {Object} event the mouseout event from the browser. - * @private - */Dygraph.prototype.mouseOut_ = function(event){if(this.getFunctionOption("unhighlightCallback")){this.getFunctionOption("unhighlightCallback").call(this,event);}if(this.getBooleanOption("hideOverlayOnMouseOut") && !this.lockedSet_){this.clearSelection();}}; /** - * Clears the current selection (i.e. points that were highlighted by moving - * the mouse over the chart). - */Dygraph.prototype.clearSelection = function(){this.cascadeEvents_('deselect',{});this.lockedSet_ = false; // Get rid of the overlay data -if(this.fadeLevel){this.animateSelection_(-1);return;}this.canvas_ctx_.clearRect(0,0,this.width_,this.height_);this.fadeLevel = 0;this.selPoints_ = [];this.lastx_ = -1;this.lastRow_ = -1;this.highlightSet_ = null;}; /** - * Returns the number of the currently selected row. To get data for this row, - * you can use the getValue method. - * @return {number} row number, or -1 if nothing is selected - */Dygraph.prototype.getSelection = function(){if(!this.selPoints_ || this.selPoints_.length < 1){return -1;}for(var setIdx=0;setIdx < this.layout_.points.length;setIdx++) {var points=this.layout_.points[setIdx];for(var row=0;row < points.length;row++) {if(points[row].x == this.selPoints_[0].x){return points[row].idx;}}}return -1;}; /** - * Returns the name of the currently-highlighted series. - * Only available when the highlightSeriesOpts option is in use. - */Dygraph.prototype.getHighlightSeries = function(){return this.highlightSet_;}; /** - * Returns true if the currently-highlighted series was locked - * via setSelection(..., seriesName, true). - */Dygraph.prototype.isSeriesLocked = function(){return this.lockedSet_;}; /** - * Fires when there's data available to be graphed. - * @param {string} data Raw CSV data to be plotted - * @private - */Dygraph.prototype.loadedEvent_ = function(data){this.rawData_ = this.parseCSV_(data);this.cascadeDataDidUpdateEvent_();this.predraw_();}; /** - * Add ticks on the x-axis representing years, months, quarters, weeks, or days - * @private - */Dygraph.prototype.addXTicks_ = function(){ // Determine the correct ticks scale on the x-axis: quarterly, monthly, ... -var range;if(this.dateWindow_){range = [this.dateWindow_[0],this.dateWindow_[1]];}else {range = this.xAxisExtremes();}var xAxisOptionsView=this.optionsViewForAxis_('x');var xTicks=xAxisOptionsView('ticker')(range[0],range[1],this.plotter_.area.w, // TODO(danvk): should be area.width -xAxisOptionsView,this); // var msg = 'ticker(' + range[0] + ', ' + range[1] + ', ' + this.width_ + ', ' + this.attr_('pixelsPerXLabel') + ') -> ' + JSON.stringify(xTicks); -// console.log(msg); -this.layout_.setXTicks(xTicks);}; /** - * Returns the correct handler class for the currently set options. - * @private - */Dygraph.prototype.getHandlerClass_ = function(){var handlerClass;if(this.attr_('dataHandler')){handlerClass = this.attr_('dataHandler');}else if(this.fractions_){if(this.getBooleanOption('errorBars')){handlerClass = _datahandlerBarsFractions2['default'];}else {handlerClass = _datahandlerDefaultFractions2['default'];}}else if(this.getBooleanOption('customBars')){handlerClass = _datahandlerBarsCustom2['default'];}else if(this.getBooleanOption('errorBars')){handlerClass = _datahandlerBarsError2['default'];}else {handlerClass = _datahandlerDefault2['default'];}return handlerClass;}; /** - * @private - * This function is called once when the chart's data is changed or the options - * dictionary is updated. It is _not_ called when the user pans or zooms. The - * idea is that values derived from the chart's data can be computed here, - * rather than every time the chart is drawn. This includes things like the - * number of axes, rolling averages, etc. - */Dygraph.prototype.predraw_ = function(){var start=new Date(); // Create the correct dataHandler -this.dataHandler_ = new (this.getHandlerClass_())();this.layout_.computePlotArea(); // TODO(danvk): move more computations out of drawGraph_ and into here. -this.computeYAxes_();if(!this.is_initial_draw_){this.canvas_ctx_.restore();this.hidden_ctx_.restore();}this.canvas_ctx_.save();this.hidden_ctx_.save(); // Create a new plotter. -this.plotter_ = new _dygraphCanvas2['default'](this,this.hidden_,this.hidden_ctx_,this.layout_); // The roller sits in the bottom left corner of the chart. We don't know where -// this will be until the options are available, so it's positioned here. -this.createRollInterface_();this.cascadeEvents_('predraw'); // Convert the raw data (a 2D array) into the internal format and compute -// rolling averages. -this.rolledSeries_ = [null]; // x-axis is the first series and it's special -for(var i=1;i < this.numColumns();i++) { // var logScale = this.attr_('logscale', i); // TODO(klausw): this looks wrong // konigsberg thinks so too. -var series=this.dataHandler_.extractSeries(this.rawData_,i,this.attributes_);if(this.rollPeriod_ > 1){series = this.dataHandler_.rollingAverage(series,this.rollPeriod_,this.attributes_);}this.rolledSeries_.push(series);} // If the data or options have changed, then we'd better redraw. -this.drawGraph_(); // This is used to determine whether to do various animations. -var end=new Date();this.drawingTimeMs_ = end - start;}; /** - * Point structure. - * - * xval_* and yval_* are the original unscaled data values, - * while x_* and y_* are scaled to the range (0.0-1.0) for plotting. - * yval_stacked is the cumulative Y value used for stacking graphs, - * and bottom/top/minus/plus are used for error bar graphs. - * - * @typedef {{ - * idx: number, - * name: string, - * x: ?number, - * xval: ?number, - * y_bottom: ?number, - * y: ?number, - * y_stacked: ?number, - * y_top: ?number, - * yval_minus: ?number, - * yval: ?number, - * yval_plus: ?number, - * yval_stacked - * }} - */Dygraph.PointType = undefined; /** - * Calculates point stacking for stackedGraph=true. - * - * For stacking purposes, interpolate or extend neighboring data across - * NaN values based on stackedGraphNaNFill settings. This is for display - * only, the underlying data value as shown in the legend remains NaN. - * - * @param {Array.} points Point array for a single series. - * Updates each Point's yval_stacked property. - * @param {Array.} cumulativeYval Accumulated top-of-graph stacked Y - * values for the series seen so far. Index is the row number. Updated - * based on the current series's values. - * @param {Array.} seriesExtremes Min and max values, updated - * to reflect the stacked values. - * @param {string} fillMethod Interpolation method, one of 'all', 'inside', or - * 'none'. - * @private - */Dygraph.stackPoints_ = function(points,cumulativeYval,seriesExtremes,fillMethod){var lastXval=null;var prevPoint=null;var nextPoint=null;var nextPointIdx=-1; // Find the next stackable point starting from the given index. -var updateNextPoint=function updateNextPoint(idx){ // If we've previously found a non-NaN point and haven't gone past it yet, -// just use that. -if(nextPointIdx >= idx)return; // We haven't found a non-NaN point yet or have moved past it, -// look towards the right to find a non-NaN point. -for(var j=idx;j < points.length;++j) { // Clear out a previously-found point (if any) since it's no longer -// valid, we shouldn't use it for interpolation anymore. -nextPoint = null;if(!isNaN(points[j].yval) && points[j].yval !== null){nextPointIdx = j;nextPoint = points[j];break;}}};for(var i=0;i < points.length;++i) {var point=points[i];var xval=point.xval;if(cumulativeYval[xval] === undefined){cumulativeYval[xval] = 0;}var actualYval=point.yval;if(isNaN(actualYval) || actualYval === null){if(fillMethod == 'none'){actualYval = 0;}else { // Interpolate/extend for stacking purposes if possible. -updateNextPoint(i);if(prevPoint && nextPoint && fillMethod != 'none'){ // Use linear interpolation between prevPoint and nextPoint. -actualYval = prevPoint.yval + (nextPoint.yval - prevPoint.yval) * ((xval - prevPoint.xval) / (nextPoint.xval - prevPoint.xval));}else if(prevPoint && fillMethod == 'all'){actualYval = prevPoint.yval;}else if(nextPoint && fillMethod == 'all'){actualYval = nextPoint.yval;}else {actualYval = 0;}}}else {prevPoint = point;}var stackedYval=cumulativeYval[xval];if(lastXval != xval){ // If an x-value is repeated, we ignore the duplicates. -stackedYval += actualYval;cumulativeYval[xval] = stackedYval;}lastXval = xval;point.yval_stacked = stackedYval;if(stackedYval > seriesExtremes[1]){seriesExtremes[1] = stackedYval;}if(stackedYval < seriesExtremes[0]){seriesExtremes[0] = stackedYval;}}}; /** - * Loop over all fields and create datasets, calculating extreme y-values for - * each series and extreme x-indices as we go. - * - * dateWindow is passed in as an explicit parameter so that we can compute - * extreme values "speculatively", i.e. without actually setting state on the - * dygraph. - * - * @param {Array.)>>} rolledSeries, where - * rolledSeries[seriesIndex][row] = raw point, where - * seriesIndex is the column number starting with 1, and - * rawPoint is [x,y] or [x, [y, err]] or [x, [y, yminus, yplus]]. - * @param {?Array.} dateWindow [xmin, xmax] pair, or null. - * @return {{ - * points: Array.>, - * seriesExtremes: Array.>, - * boundaryIds: Array.}} - * @private - */Dygraph.prototype.gatherDatasets_ = function(rolledSeries,dateWindow){var boundaryIds=[];var points=[];var cumulativeYval=[]; // For stacked series. -var extremes={}; // series name -> [low, high] -var seriesIdx,sampleIdx;var firstIdx,lastIdx;var axisIdx; // Loop over the fields (series). Go from the last to the first, -// because if they're stacked that's how we accumulate the values. -var num_series=rolledSeries.length - 1;var series;for(seriesIdx = num_series;seriesIdx >= 1;seriesIdx--) {if(!this.visibility()[seriesIdx - 1])continue; // Prune down to the desired range, if necessary (for zooming) -// Because there can be lines going to points outside of the visible area, -// we actually prune to visible points, plus one on either side. -if(dateWindow){series = rolledSeries[seriesIdx];var low=dateWindow[0];var high=dateWindow[1]; // TODO(danvk): do binary search instead of linear search. -// TODO(danvk): pass firstIdx and lastIdx directly to the renderer. -firstIdx = null;lastIdx = null;for(sampleIdx = 0;sampleIdx < series.length;sampleIdx++) {if(series[sampleIdx][0] >= low && firstIdx === null){firstIdx = sampleIdx;}if(series[sampleIdx][0] <= high){lastIdx = sampleIdx;}}if(firstIdx === null)firstIdx = 0;var correctedFirstIdx=firstIdx;var isInvalidValue=true;while(isInvalidValue && correctedFirstIdx > 0) {correctedFirstIdx--; // check if the y value is null. -isInvalidValue = series[correctedFirstIdx][1] === null;}if(lastIdx === null)lastIdx = series.length - 1;var correctedLastIdx=lastIdx;isInvalidValue = true;while(isInvalidValue && correctedLastIdx < series.length - 1) {correctedLastIdx++;isInvalidValue = series[correctedLastIdx][1] === null;}if(correctedFirstIdx !== firstIdx){firstIdx = correctedFirstIdx;}if(correctedLastIdx !== lastIdx){lastIdx = correctedLastIdx;}boundaryIds[seriesIdx - 1] = [firstIdx,lastIdx]; // .slice's end is exclusive, we want to include lastIdx. -series = series.slice(firstIdx,lastIdx + 1);}else {series = rolledSeries[seriesIdx];boundaryIds[seriesIdx - 1] = [0,series.length - 1];}var seriesName=this.attr_("labels")[seriesIdx];var seriesExtremes=this.dataHandler_.getExtremeYValues(series,dateWindow,this.getBooleanOption("stepPlot",seriesName));var seriesPoints=this.dataHandler_.seriesToPoints(series,seriesName,boundaryIds[seriesIdx - 1][0]);if(this.getBooleanOption("stackedGraph")){axisIdx = this.attributes_.axisForSeries(seriesName);if(cumulativeYval[axisIdx] === undefined){cumulativeYval[axisIdx] = [];}Dygraph.stackPoints_(seriesPoints,cumulativeYval[axisIdx],seriesExtremes,this.getBooleanOption("stackedGraphNaNFill"));}extremes[seriesName] = seriesExtremes;points[seriesIdx] = seriesPoints;}return {points:points,extremes:extremes,boundaryIds:boundaryIds};}; /** - * Update the graph with new data. This method is called when the viewing area - * has changed. If the underlying data or options have changed, predraw_ will - * be called before drawGraph_ is called. - * - * @private - */Dygraph.prototype.drawGraph_ = function(){var start=new Date(); // This is used to set the second parameter to drawCallback, below. -var is_initial_draw=this.is_initial_draw_;this.is_initial_draw_ = false;this.layout_.removeAllDatasets();this.setColors_();this.attrs_.pointSize = 0.5 * this.getNumericOption('highlightCircleSize');var packed=this.gatherDatasets_(this.rolledSeries_,this.dateWindow_);var points=packed.points;var extremes=packed.extremes;this.boundaryIds_ = packed.boundaryIds;this.setIndexByName_ = {};var labels=this.attr_("labels");var dataIdx=0;for(var i=1;i < points.length;i++) {if(!this.visibility()[i - 1])continue;this.layout_.addDataset(labels[i],points[i]);this.datasetIndex_[i] = dataIdx++;}for(var i=0;i < labels.length;i++) {this.setIndexByName_[labels[i]] = i;}this.computeYAxisRanges_(extremes);this.layout_.setYAxes(this.axes_);this.addXTicks_(); // Tell PlotKit to use this new data and render itself -this.layout_.evaluate();this.renderGraph_(is_initial_draw);if(this.getStringOption("timingName")){var end=new Date();console.log(this.getStringOption("timingName") + " - drawGraph: " + (end - start) + "ms");}}; /** - * This does the work of drawing the chart. It assumes that the layout and axis - * scales have already been set (e.g. by predraw_). - * - * @private - */Dygraph.prototype.renderGraph_ = function(is_initial_draw){this.cascadeEvents_('clearChart');this.plotter_.clear();var underlayCallback=this.getFunctionOption('underlayCallback');if(underlayCallback){ // NOTE: we pass the dygraph object to this callback twice to avoid breaking -// users who expect a deprecated form of this callback. -underlayCallback.call(this,this.hidden_ctx_,this.layout_.getPlotArea(),this,this);}var e={canvas:this.hidden_,drawingContext:this.hidden_ctx_};this.cascadeEvents_('willDrawChart',e);this.plotter_.render();this.cascadeEvents_('didDrawChart',e);this.lastRow_ = -1; // because plugins/legend.js clears the legend -// TODO(danvk): is this a performance bottleneck when panning? -// The interaction canvas should already be empty in that situation. -this.canvas_.getContext('2d').clearRect(0,0,this.width_,this.height_);var drawCallback=this.getFunctionOption("drawCallback");if(drawCallback !== null){drawCallback.call(this,this,is_initial_draw);}if(is_initial_draw){this.readyFired_ = true;while(this.readyFns_.length > 0) {var fn=this.readyFns_.pop();fn(this);}}}; /** - * @private - * Determine properties of the y-axes which are independent of the data - * currently being displayed. This includes things like the number of axes and - * the style of the axes. It does not include the range of each axis and its - * tick marks. - * This fills in this.axes_. - * axes_ = [ { options } ] - * indices are into the axes_ array. - */Dygraph.prototype.computeYAxes_ = function(){var axis,index,opts,v; // this.axes_ doesn't match this.attributes_.axes_.options. It's used for -// data computation as well as options storage. -// Go through once and add all the axes. -this.axes_ = [];for(axis = 0;axis < this.attributes_.numAxes();axis++) { // Add a new axis, making a copy of its per-axis options. -opts = {g:this};utils.update(opts,this.attributes_.axisOptions(axis));this.axes_[axis] = opts;}for(axis = 0;axis < this.axes_.length;axis++) {if(axis === 0){opts = this.optionsViewForAxis_('y' + (axis?'2':''));v = opts("valueRange");if(v)this.axes_[axis].valueRange = v;}else { // To keep old behavior -var axes=this.user_attrs_.axes;if(axes && axes.y2){v = axes.y2.valueRange;if(v)this.axes_[axis].valueRange = v;}}}}; /** - * Returns the number of y-axes on the chart. - * @return {number} the number of axes. - */Dygraph.prototype.numAxes = function(){return this.attributes_.numAxes();}; /** - * @private - * Returns axis properties for the given series. - * @param {string} setName The name of the series for which to get axis - * properties, e.g. 'Y1'. - * @return {Object} The axis properties. - */Dygraph.prototype.axisPropertiesForSeries = function(series){ // TODO(danvk): handle errors. -return this.axes_[this.attributes_.axisForSeries(series)];}; /** - * @private - * Determine the value range and tick marks for each axis. - * @param {Object} extremes A mapping from seriesName -> [low, high] - * This fills in the valueRange and ticks fields in each entry of this.axes_. - */Dygraph.prototype.computeYAxisRanges_ = function(extremes){var isNullUndefinedOrNaN=function isNullUndefinedOrNaN(num){return isNaN(parseFloat(num));};var numAxes=this.attributes_.numAxes();var ypadCompat,span,series,ypad;var p_axis; // Compute extreme values, a span and tick marks for each axis. -for(var i=0;i < numAxes;i++) {var axis=this.axes_[i];var logscale=this.attributes_.getForAxis("logscale",i);var includeZero=this.attributes_.getForAxis("includeZero",i);var independentTicks=this.attributes_.getForAxis("independentTicks",i);series = this.attributes_.seriesForAxis(i); // Add some padding. This supports two Y padding operation modes: -// -// - backwards compatible (yRangePad not set): -// 10% padding for automatic Y ranges, but not for user-supplied -// ranges, and move a close-to-zero edge to zero, since drawing at the edge -// results in invisible lines. Unfortunately lines drawn at the edge of a -// user-supplied range will still be invisible. If logscale is -// set, add a variable amount of padding at the top but -// none at the bottom. -// -// - new-style (yRangePad set by the user): -// always add the specified Y padding. -// -ypadCompat = true;ypad = 0.1; // add 10% -var yRangePad=this.getNumericOption('yRangePad');if(yRangePad !== null){ypadCompat = false; // Convert pixel padding to ratio -ypad = yRangePad / this.plotter_.area.h;}if(series.length === 0){ // If no series are defined or visible then use a reasonable default -axis.extremeRange = [0,1];}else { // Calculate the extremes of extremes. -var minY=Infinity; // extremes[series[0]][0]; -var maxY=-Infinity; // extremes[series[0]][1]; -var extremeMinY,extremeMaxY;for(var j=0;j < series.length;j++) { // this skips invisible series -if(!extremes.hasOwnProperty(series[j]))continue; // Only use valid extremes to stop null data series' from corrupting the scale. -extremeMinY = extremes[series[j]][0];if(extremeMinY !== null){minY = Math.min(extremeMinY,minY);}extremeMaxY = extremes[series[j]][1];if(extremeMaxY !== null){maxY = Math.max(extremeMaxY,maxY);}} // Include zero if requested by the user. -if(includeZero && !logscale){if(minY > 0)minY = 0;if(maxY < 0)maxY = 0;} // Ensure we have a valid scale, otherwise default to [0, 1] for safety. -if(minY == Infinity)minY = 0;if(maxY == -Infinity)maxY = 1;span = maxY - minY; // special case: if we have no sense of scale, center on the sole value. -if(span === 0){if(maxY !== 0){span = Math.abs(maxY);}else { // ... and if the sole value is zero, use range 0-1. -maxY = 1;span = 1;}}var maxAxisY=maxY,minAxisY=minY;if(ypadCompat){if(logscale){maxAxisY = maxY + ypad * span;minAxisY = minY;}else {maxAxisY = maxY + ypad * span;minAxisY = minY - ypad * span; // Backwards-compatible behavior: Move the span to start or end at zero if it's -// close to zero. -if(minAxisY < 0 && minY >= 0)minAxisY = 0;if(maxAxisY > 0 && maxY <= 0)maxAxisY = 0;}}axis.extremeRange = [minAxisY,maxAxisY];}if(axis.valueRange){ // This is a user-set value range for this axis. -var y0=isNullUndefinedOrNaN(axis.valueRange[0])?axis.extremeRange[0]:axis.valueRange[0];var y1=isNullUndefinedOrNaN(axis.valueRange[1])?axis.extremeRange[1]:axis.valueRange[1];axis.computedValueRange = [y0,y1];}else {axis.computedValueRange = axis.extremeRange;}if(!ypadCompat){ // When using yRangePad, adjust the upper/lower bounds to add -// padding unless the user has zoomed/panned the Y axis range. -if(logscale){y0 = axis.computedValueRange[0];y1 = axis.computedValueRange[1];var y0pct=ypad / (2 * ypad - 1);var y1pct=(ypad - 1) / (2 * ypad - 1);axis.computedValueRange[0] = utils.logRangeFraction(y0,y1,y0pct);axis.computedValueRange[1] = utils.logRangeFraction(y0,y1,y1pct);}else {y0 = axis.computedValueRange[0];y1 = axis.computedValueRange[1];span = y1 - y0;axis.computedValueRange[0] = y0 - span * ypad;axis.computedValueRange[1] = y1 + span * ypad;}}if(independentTicks){axis.independentTicks = independentTicks;var opts=this.optionsViewForAxis_('y' + (i?'2':''));var ticker=opts('ticker');axis.ticks = ticker(axis.computedValueRange[0],axis.computedValueRange[1],this.plotter_.area.h,opts,this); // Define the first independent axis as primary axis. -if(!p_axis)p_axis = axis;}}if(p_axis === undefined){throw "Configuration Error: At least one axis has to have the \"independentTicks\" option activated.";} // Add ticks. By default, all axes inherit the tick positions of the -// primary axis. However, if an axis is specifically marked as having -// independent ticks, then that is permissible as well. -for(var i=0;i < numAxes;i++) {var axis=this.axes_[i];if(!axis.independentTicks){var opts=this.optionsViewForAxis_('y' + (i?'2':''));var ticker=opts('ticker');var p_ticks=p_axis.ticks;var p_scale=p_axis.computedValueRange[1] - p_axis.computedValueRange[0];var scale=axis.computedValueRange[1] - axis.computedValueRange[0];var tick_values=[];for(var k=0;k < p_ticks.length;k++) {var y_frac=(p_ticks[k].v - p_axis.computedValueRange[0]) / p_scale;var y_val=axis.computedValueRange[0] + y_frac * scale;tick_values.push(y_val);}axis.ticks = ticker(axis.computedValueRange[0],axis.computedValueRange[1],this.plotter_.area.h,opts,this,tick_values);}}}; /** - * Detects the type of the str (date or numeric) and sets the various - * formatting attributes in this.attrs_ based on this type. - * @param {string} str An x value. - * @private - */Dygraph.prototype.detectTypeFromString_ = function(str){var isDate=false;var dashPos=str.indexOf('-'); // could be 2006-01-01 _or_ 1.0e-2 -if(dashPos > 0 && str[dashPos - 1] != 'e' && str[dashPos - 1] != 'E' || str.indexOf('/') >= 0 || isNaN(parseFloat(str))){isDate = true;}else if(str.length == 8 && str > '19700101' && str < '20371231'){ // TODO(danvk): remove support for this format. -isDate = true;}this.setXAxisOptions_(isDate);};Dygraph.prototype.setXAxisOptions_ = function(isDate){if(isDate){this.attrs_.xValueParser = utils.dateParser;this.attrs_.axes.x.valueFormatter = utils.dateValueFormatter;this.attrs_.axes.x.ticker = DygraphTickers.dateTicker;this.attrs_.axes.x.axisLabelFormatter = utils.dateAxisLabelFormatter;}else { /** @private (shut up, jsdoc!) */this.attrs_.xValueParser = function(x){return parseFloat(x);}; // TODO(danvk): use Dygraph.numberValueFormatter here? -/** @private (shut up, jsdoc!) */this.attrs_.axes.x.valueFormatter = function(x){return x;};this.attrs_.axes.x.ticker = DygraphTickers.numericTicks;this.attrs_.axes.x.axisLabelFormatter = this.attrs_.axes.x.valueFormatter;}}; /** - * @private - * Parses a string in a special csv format. We expect a csv file where each - * line is a date point, and the first field in each line is the date string. - * We also expect that all remaining fields represent series. - * if the errorBars attribute is set, then interpret the fields as: - * date, series1, stddev1, series2, stddev2, ... - * @param {[Object]} data See above. - * - * @return [Object] An array with one entry for each row. These entries - * are an array of cells in that row. The first entry is the parsed x-value for - * the row. The second, third, etc. are the y-values. These can take on one of - * three forms, depending on the CSV and constructor parameters: - * 1. numeric value - * 2. [ value, stddev ] - * 3. [ low value, center value, high value ] - */Dygraph.prototype.parseCSV_ = function(data){var ret=[];var line_delimiter=utils.detectLineDelimiter(data);var lines=data.split(line_delimiter || "\n");var vals,j; // Use the default delimiter or fall back to a tab if that makes sense. -var delim=this.getStringOption('delimiter');if(lines[0].indexOf(delim) == -1 && lines[0].indexOf('\t') >= 0){delim = '\t';}var start=0;if(!('labels' in this.user_attrs_)){ // User hasn't explicitly set labels, so they're (presumably) in the CSV. -start = 1;this.attrs_.labels = lines[0].split(delim); // NOTE: _not_ user_attrs_. -this.attributes_.reparseSeries();}var line_no=0;var xParser;var defaultParserSet=false; // attempt to auto-detect x value type -var expectedCols=this.attr_("labels").length;var outOfOrder=false;for(var i=start;i < lines.length;i++) {var line=lines[i];line_no = i;if(line.length === 0)continue; // skip blank lines -if(line[0] == '#')continue; // skip comment lines -var inFields=line.split(delim);if(inFields.length < 2)continue;var fields=[];if(!defaultParserSet){this.detectTypeFromString_(inFields[0]);xParser = this.getFunctionOption("xValueParser");defaultParserSet = true;}fields[0] = xParser(inFields[0],this); // If fractions are expected, parse the numbers as "A/B" -if(this.fractions_){for(j = 1;j < inFields.length;j++) { // TODO(danvk): figure out an appropriate way to flag parse errors. -vals = inFields[j].split("/");if(vals.length != 2){console.error('Expected fractional "num/den" values in CSV data ' + "but found a value '" + inFields[j] + "' on line " + (1 + i) + " ('" + line + "') which is not of this form.");fields[j] = [0,0];}else {fields[j] = [utils.parseFloat_(vals[0],i,line),utils.parseFloat_(vals[1],i,line)];}}}else if(this.getBooleanOption("errorBars")){ // If there are error bars, values are (value, stddev) pairs -if(inFields.length % 2 != 1){console.error('Expected alternating (value, stdev.) pairs in CSV data ' + 'but line ' + (1 + i) + ' has an odd number of values (' + (inFields.length - 1) + "): '" + line + "'");}for(j = 1;j < inFields.length;j += 2) {fields[(j + 1) / 2] = [utils.parseFloat_(inFields[j],i,line),utils.parseFloat_(inFields[j + 1],i,line)];}}else if(this.getBooleanOption("customBars")){ // Bars are a low;center;high tuple -for(j = 1;j < inFields.length;j++) {var val=inFields[j];if(/^ *$/.test(val)){fields[j] = [null,null,null];}else {vals = val.split(";");if(vals.length == 3){fields[j] = [utils.parseFloat_(vals[0],i,line),utils.parseFloat_(vals[1],i,line),utils.parseFloat_(vals[2],i,line)];}else {console.warn('When using customBars, values must be either blank ' + 'or "low;center;high" tuples (got "' + val + '" on line ' + (1 + i));}}}}else { // Values are just numbers -for(j = 1;j < inFields.length;j++) {fields[j] = utils.parseFloat_(inFields[j],i,line);}}if(ret.length > 0 && fields[0] < ret[ret.length - 1][0]){outOfOrder = true;}if(fields.length != expectedCols){console.error("Number of columns in line " + i + " (" + fields.length + ") does not agree with number of labels (" + expectedCols + ") " + line);} // If the user specified the 'labels' option and none of the cells of the -// first row parsed correctly, then they probably double-specified the -// labels. We go with the values set in the option, discard this row and -// log a warning to the JS console. -if(i === 0 && this.attr_('labels')){var all_null=true;for(j = 0;all_null && j < fields.length;j++) {if(fields[j])all_null = false;}if(all_null){console.warn("The dygraphs 'labels' option is set, but the first row " + "of CSV data ('" + line + "') appears to also contain " + "labels. Will drop the CSV labels and use the option " + "labels.");continue;}}ret.push(fields);}if(outOfOrder){console.warn("CSV is out of order; order it correctly to speed loading.");ret.sort(function(a,b){return a[0] - b[0];});}return ret;}; // In native format, all values must be dates or numbers. -// This check isn't perfect but will catch most mistaken uses of strings. -function validateNativeFormat(data){var firstRow=data[0];var firstX=firstRow[0];if(typeof firstX !== 'number' && !utils.isDateLike(firstX)){throw new Error('Expected number or date but got ' + typeof firstX + ': ' + firstX + '.');}for(var i=1;i < firstRow.length;i++) {var val=firstRow[i];if(val === null || val === undefined)continue;if(typeof val === 'number')continue;if(utils.isArrayLike(val))continue; // e.g. error bars or custom bars. -throw new Error('Expected number or array but got ' + typeof val + ': ' + val + '.');}} /** - * The user has provided their data as a pre-packaged JS array. If the x values - * are numeric, this is the same as dygraphs' internal format. If the x values - * are dates, we need to convert them from Date objects to ms since epoch. - * @param {!Array} data - * @return {Object} data with numeric x values. - * @private - */Dygraph.prototype.parseArray_ = function(data){ // Peek at the first x value to see if it's numeric. -if(data.length === 0){console.error("Can't plot empty data set");return null;}if(data[0].length === 0){console.error("Data set cannot contain an empty row");return null;}validateNativeFormat(data);var i;if(this.attr_("labels") === null){console.warn("Using default labels. Set labels explicitly via 'labels' " + "in the options parameter");this.attrs_.labels = ["X"];for(i = 1;i < data[0].length;i++) {this.attrs_.labels.push("Y" + i); // Not user_attrs_. -}this.attributes_.reparseSeries();}else {var num_labels=this.attr_("labels");if(num_labels.length != data[0].length){console.error("Mismatch between number of labels (" + num_labels + ")" + " and number of columns in array (" + data[0].length + ")");return null;}}if(utils.isDateLike(data[0][0])){ // Some intelligent defaults for a date x-axis. -this.attrs_.axes.x.valueFormatter = utils.dateValueFormatter;this.attrs_.axes.x.ticker = DygraphTickers.dateTicker;this.attrs_.axes.x.axisLabelFormatter = utils.dateAxisLabelFormatter; // Assume they're all dates. -var parsedData=utils.clone(data);for(i = 0;i < data.length;i++) {if(parsedData[i].length === 0){console.error("Row " + (1 + i) + " of data is empty");return null;}if(parsedData[i][0] === null || typeof parsedData[i][0].getTime != 'function' || isNaN(parsedData[i][0].getTime())){console.error("x value in row " + (1 + i) + " is not a Date");return null;}parsedData[i][0] = parsedData[i][0].getTime();}return parsedData;}else { // Some intelligent defaults for a numeric x-axis. -/** @private (shut up, jsdoc!) */this.attrs_.axes.x.valueFormatter = function(x){return x;};this.attrs_.axes.x.ticker = DygraphTickers.numericTicks;this.attrs_.axes.x.axisLabelFormatter = utils.numberAxisLabelFormatter;return data;}}; /** - * Parses a DataTable object from gviz. - * The data is expected to have a first column that is either a date or a - * number. All subsequent columns must be numbers. If there is a clear mismatch - * between this.xValueParser_ and the type of the first column, it will be - * fixed. Fills out rawData_. - * @param {!google.visualization.DataTable} data See above. - * @private - */Dygraph.prototype.parseDataTable_ = function(data){var shortTextForAnnotationNum=function shortTextForAnnotationNum(num){ // converts [0-9]+ [A-Z][a-z]* -// example: 0=A, 1=B, 25=Z, 26=Aa, 27=Ab -// and continues like.. Ba Bb .. Za .. Zz..Aaa...Zzz Aaaa Zzzz -var shortText=String.fromCharCode(65 /* A */ + num % 26);num = Math.floor(num / 26);while(num > 0) {shortText = String.fromCharCode(65 /* A */ + (num - 1) % 26) + shortText.toLowerCase();num = Math.floor((num - 1) / 26);}return shortText;};var cols=data.getNumberOfColumns();var rows=data.getNumberOfRows();var indepType=data.getColumnType(0);if(indepType == 'date' || indepType == 'datetime'){this.attrs_.xValueParser = utils.dateParser;this.attrs_.axes.x.valueFormatter = utils.dateValueFormatter;this.attrs_.axes.x.ticker = DygraphTickers.dateTicker;this.attrs_.axes.x.axisLabelFormatter = utils.dateAxisLabelFormatter;}else if(indepType == 'number'){this.attrs_.xValueParser = function(x){return parseFloat(x);};this.attrs_.axes.x.valueFormatter = function(x){return x;};this.attrs_.axes.x.ticker = DygraphTickers.numericTicks;this.attrs_.axes.x.axisLabelFormatter = this.attrs_.axes.x.valueFormatter;}else {throw new Error("only 'date', 'datetime' and 'number' types are supported " + "for column 1 of DataTable input (Got '" + indepType + "')");} // Array of the column indices which contain data (and not annotations). -var colIdx=[];var annotationCols={}; // data index -> [annotation cols] -var hasAnnotations=false;var i,j;for(i = 1;i < cols;i++) {var type=data.getColumnType(i);if(type == 'number'){colIdx.push(i);}else if(type == 'string' && this.getBooleanOption('displayAnnotations')){ // This is OK -- it's an annotation column. -var dataIdx=colIdx[colIdx.length - 1];if(!annotationCols.hasOwnProperty(dataIdx)){annotationCols[dataIdx] = [i];}else {annotationCols[dataIdx].push(i);}hasAnnotations = true;}else {throw new Error("Only 'number' is supported as a dependent type with Gviz." + " 'string' is only supported if displayAnnotations is true");}} // Read column labels -// TODO(danvk): add support back for errorBars -var labels=[data.getColumnLabel(0)];for(i = 0;i < colIdx.length;i++) {labels.push(data.getColumnLabel(colIdx[i]));if(this.getBooleanOption("errorBars"))i += 1;}this.attrs_.labels = labels;cols = labels.length;var ret=[];var outOfOrder=false;var annotations=[];for(i = 0;i < rows;i++) {var row=[];if(typeof data.getValue(i,0) === 'undefined' || data.getValue(i,0) === null){console.warn("Ignoring row " + i + " of DataTable because of undefined or null first column.");continue;}if(indepType == 'date' || indepType == 'datetime'){row.push(data.getValue(i,0).getTime());}else {row.push(data.getValue(i,0));}if(!this.getBooleanOption("errorBars")){for(j = 0;j < colIdx.length;j++) {var col=colIdx[j];row.push(data.getValue(i,col));if(hasAnnotations && annotationCols.hasOwnProperty(col) && data.getValue(i,annotationCols[col][0]) !== null){var ann={};ann.series = data.getColumnLabel(col);ann.xval = row[0];ann.shortText = shortTextForAnnotationNum(annotations.length);ann.text = '';for(var k=0;k < annotationCols[col].length;k++) {if(k)ann.text += "\n";ann.text += data.getValue(i,annotationCols[col][k]);}annotations.push(ann);}} // Strip out infinities, which give dygraphs problems later on. -for(j = 0;j < row.length;j++) {if(!isFinite(row[j]))row[j] = null;}}else {for(j = 0;j < cols - 1;j++) {row.push([data.getValue(i,1 + 2 * j),data.getValue(i,2 + 2 * j)]);}}if(ret.length > 0 && row[0] < ret[ret.length - 1][0]){outOfOrder = true;}ret.push(row);}if(outOfOrder){console.warn("DataTable is out of order; order it correctly to speed loading.");ret.sort(function(a,b){return a[0] - b[0];});}this.rawData_ = ret;if(annotations.length > 0){this.setAnnotations(annotations,true);}this.attributes_.reparseSeries();}; /** - * Signals to plugins that the chart data has updated. - * This happens after the data has updated but before the chart has redrawn. - * @private - */Dygraph.prototype.cascadeDataDidUpdateEvent_ = function(){ // TODO(danvk): there are some issues checking xAxisRange() and using -// toDomCoords from handlers of this event. The visible range should be set -// when the chart is drawn, not derived from the data. -this.cascadeEvents_('dataDidUpdate',{});}; /** - * Get the CSV data. If it's in a function, call that function. If it's in a - * file, do an XMLHttpRequest to get it. - * @private - */Dygraph.prototype.start_ = function(){var data=this.file_; // Functions can return references of all other types. -if(typeof data == 'function'){data = data();}if(utils.isArrayLike(data)){this.rawData_ = this.parseArray_(data);this.cascadeDataDidUpdateEvent_();this.predraw_();}else if(typeof data == 'object' && typeof data.getColumnRange == 'function'){ // must be a DataTable from gviz. -this.parseDataTable_(data);this.cascadeDataDidUpdateEvent_();this.predraw_();}else if(typeof data == 'string'){ // Heuristic: a newline means it's CSV data. Otherwise it's an URL. -var line_delimiter=utils.detectLineDelimiter(data);if(line_delimiter){this.loadedEvent_(data);}else { // REMOVE_FOR_IE -var req;if(window.XMLHttpRequest){ // Firefox, Opera, IE7, and other browsers will use the native object -req = new XMLHttpRequest();}else { // IE 5 and 6 will use the ActiveX control -req = new ActiveXObject("Microsoft.XMLHTTP");}var caller=this;req.onreadystatechange = function(){if(req.readyState == 4){if(req.status === 200 || // Normal http -req.status === 0){ // Chrome w/ --allow-file-access-from-files -caller.loadedEvent_(req.responseText);}}};req.open("GET",data,true);req.send(null);}}else {console.error("Unknown data format: " + typeof data);}}; /** - * Changes various properties of the graph. These can include: - *
    - *
  • file: changes the source data for the graph
  • - *
  • errorBars: changes whether the data contains stddev
  • - *
- * - * There's a huge variety of options that can be passed to this method. For a - * full list, see http://dygraphs.com/options.html. - * - * @param {Object} input_attrs The new properties and values - * @param {boolean} block_redraw Usually the chart is redrawn after every - * call to updateOptions(). If you know better, you can pass true to - * explicitly block the redraw. This can be useful for chaining - * updateOptions() calls, avoiding the occasional infinite loop and - * preventing redraws when it's not necessary (e.g. when updating a - * callback). - */Dygraph.prototype.updateOptions = function(input_attrs,block_redraw){if(typeof block_redraw == 'undefined')block_redraw = false; // copyUserAttrs_ drops the "file" parameter as a convenience to us. -var file=input_attrs.file;var attrs=Dygraph.copyUserAttrs_(input_attrs); // TODO(danvk): this is a mess. Move these options into attr_. -if('rollPeriod' in attrs){this.rollPeriod_ = attrs.rollPeriod;}if('dateWindow' in attrs){this.dateWindow_ = attrs.dateWindow;} // TODO(danvk): validate per-series options. -// Supported: -// strokeWidth -// pointSize -// drawPoints -// highlightCircleSize -// Check if this set options will require new points. -var requiresNewPoints=utils.isPixelChangingOptionList(this.attr_("labels"),attrs);utils.updateDeep(this.user_attrs_,attrs);this.attributes_.reparseSeries();if(file){ // This event indicates that the data is about to change, but hasn't yet. -// TODO(danvk): support cancellation of the update via this event. -this.cascadeEvents_('dataWillUpdate',{});this.file_ = file;if(!block_redraw)this.start_();}else {if(!block_redraw){if(requiresNewPoints){this.predraw_();}else {this.renderGraph_(false);}}}}; /** - * Make a copy of input attributes, removing file as a convenience. - * @private - */Dygraph.copyUserAttrs_ = function(attrs){var my_attrs={};for(var k in attrs) {if(!attrs.hasOwnProperty(k))continue;if(k == 'file')continue;if(attrs.hasOwnProperty(k))my_attrs[k] = attrs[k];}return my_attrs;}; /** - * Resizes the dygraph. If no parameters are specified, resizes to fill the - * containing div (which has presumably changed size since the dygraph was - * instantiated. If the width/height are specified, the div will be resized. - * - * This is far more efficient than destroying and re-instantiating a - * Dygraph, since it doesn't have to reparse the underlying data. - * - * @param {number} width Width (in pixels) - * @param {number} height Height (in pixels) - */Dygraph.prototype.resize = function(width,height){if(this.resize_lock){return;}this.resize_lock = true;if(width === null != (height === null)){console.warn("Dygraph.resize() should be called with zero parameters or " + "two non-NULL parameters. Pretending it was zero.");width = height = null;}var old_width=this.width_;var old_height=this.height_;if(width){this.maindiv_.style.width = width + "px";this.maindiv_.style.height = height + "px";this.width_ = width;this.height_ = height;}else {this.width_ = this.maindiv_.clientWidth;this.height_ = this.maindiv_.clientHeight;}if(old_width != this.width_ || old_height != this.height_){ // Resizing a canvas erases it, even when the size doesn't change, so -// any resize needs to be followed by a redraw. -this.resizeElements_();this.predraw_();}this.resize_lock = false;}; /** - * Adjusts the number of points in the rolling average. Updates the graph to - * reflect the new averaging period. - * @param {number} length Number of points over which to average the data. - */Dygraph.prototype.adjustRoll = function(length){this.rollPeriod_ = length;this.predraw_();}; /** - * Returns a boolean array of visibility statuses. - */Dygraph.prototype.visibility = function(){ // Do lazy-initialization, so that this happens after we know the number of -// data series. -if(!this.getOption("visibility")){this.attrs_.visibility = [];} // TODO(danvk): it looks like this could go into an infinite loop w/ user_attrs. -while(this.getOption("visibility").length < this.numColumns() - 1) {this.attrs_.visibility.push(true);}return this.getOption("visibility");}; /** - * Changes the visibility of one or more series. - * - * @param {number|number[]|object} num the series index or an array of series indices - * or a boolean array of visibility states by index - * or an object mapping series numbers, as keys, to - * visibility state (boolean values) - * @param {boolean} value the visibility state expressed as a boolean - */Dygraph.prototype.setVisibility = function(num,value){var x=this.visibility();var numIsObject=false;if(!Array.isArray(num)){if(num !== null && typeof num === 'object'){numIsObject = true;}else {num = [num];}}if(numIsObject){for(var i in num) {if(num.hasOwnProperty(i)){if(i < 0 || i >= x.length){console.warn("Invalid series number in setVisibility: " + i);}else {x[i] = num[i];}}}}else {for(var i=0;i < num.length;i++) {if(typeof num[i] === 'boolean'){if(i >= x.length){console.warn("Invalid series number in setVisibility: " + i);}else {x[i] = num[i];}}else {if(num[i] < 0 || num[i] >= x.length){console.warn("Invalid series number in setVisibility: " + num[i]);}else {x[num[i]] = value;}}}}this.predraw_();}; /** - * How large of an area will the dygraph render itself in? - * This is used for testing. - * @return A {width: w, height: h} object. - * @private - */Dygraph.prototype.size = function(){return {width:this.width_,height:this.height_};}; /** - * Update the list of annotations and redraw the chart. - * See dygraphs.com/annotations.html for more info on how to use annotations. - * @param ann {Array} An array of annotation objects. - * @param suppressDraw {Boolean} Set to "true" to block chart redraw (optional). - */Dygraph.prototype.setAnnotations = function(ann,suppressDraw){ // Only add the annotation CSS rule once we know it will be used. -this.annotations_ = ann;if(!this.layout_){console.warn("Tried to setAnnotations before dygraph was ready. " + "Try setting them in a ready() block. See " + "dygraphs.com/tests/annotation.html");return;}this.layout_.setAnnotations(this.annotations_);if(!suppressDraw){this.predraw_();}}; /** - * Return the list of annotations. - */Dygraph.prototype.annotations = function(){return this.annotations_;}; /** - * Get the list of label names for this graph. The first column is the - * x-axis, so the data series names start at index 1. - * - * Returns null when labels have not yet been defined. - */Dygraph.prototype.getLabels = function(){var labels=this.attr_("labels");return labels?labels.slice():null;}; /** - * Get the index of a series (column) given its name. The first column is the - * x-axis, so the data series start with index 1. - */Dygraph.prototype.indexFromSetName = function(name){return this.setIndexByName_[name];}; /** - * Find the row number corresponding to the given x-value. - * Returns null if there is no such x-value in the data. - * If there are multiple rows with the same x-value, this will return the - * first one. - * @param {number} xVal The x-value to look for (e.g. millis since epoch). - * @return {?number} The row number, which you can pass to getValue(), or null. - */Dygraph.prototype.getRowForX = function(xVal){var low=0,high=this.numRows() - 1;while(low <= high) {var idx=high + low >> 1;var x=this.getValue(idx,0);if(x < xVal){low = idx + 1;}else if(x > xVal){high = idx - 1;}else if(low != idx){ // equal, but there may be an earlier match. -high = idx;}else {return idx;}}return null;}; /** - * Trigger a callback when the dygraph has drawn itself and is ready to be - * manipulated. This is primarily useful when dygraphs has to do an XHR for the - * data (i.e. a URL is passed as the data source) and the chart is drawn - * asynchronously. If the chart has already drawn, the callback will fire - * immediately. - * - * This is a good place to call setAnnotation(). - * - * @param {function(!Dygraph)} callback The callback to trigger when the chart - * is ready. - */Dygraph.prototype.ready = function(callback){if(this.is_initial_draw_){this.readyFns_.push(callback);}else {callback.call(this,this);}}; /** - * Add an event handler. This event handler is kept until the graph is - * destroyed with a call to graph.destroy(). - * - * @param {!Node} elem The element to add the event to. - * @param {string} type The type of the event, e.g. 'click' or 'mousemove'. - * @param {function(Event):(boolean|undefined)} fn The function to call - * on the event. The function takes one parameter: the event object. - * @private - */Dygraph.prototype.addAndTrackEvent = function(elem,type,fn){utils.addEvent(elem,type,fn);this.registeredEvents_.push({elem:elem,type:type,fn:fn});};Dygraph.prototype.removeTrackedEvents_ = function(){if(this.registeredEvents_){for(var idx=0;idx < this.registeredEvents_.length;idx++) {var reg=this.registeredEvents_[idx];utils.removeEvent(reg.elem,reg.type,reg.fn);}}this.registeredEvents_ = [];}; // Installed plugins, in order of precedence (most-general to most-specific). -Dygraph.PLUGINS = [_pluginsLegend2['default'],_pluginsAxes2['default'],_pluginsRangeSelector2['default'], // Has to be before ChartLabels so that its callbacks are called after ChartLabels' callbacks. -_pluginsChartLabels2['default'],_pluginsAnnotations2['default'],_pluginsGrid2['default']]; // There are many symbols which have historically been available through the -// Dygraph class. These are exported here for backwards compatibility. -Dygraph.GVizChart = _dygraphGviz2['default'];Dygraph.DASHED_LINE = utils.DASHED_LINE;Dygraph.DOT_DASH_LINE = utils.DOT_DASH_LINE;Dygraph.dateAxisLabelFormatter = utils.dateAxisLabelFormatter;Dygraph.toRGB_ = utils.toRGB_;Dygraph.findPos = utils.findPos;Dygraph.pageX = utils.pageX;Dygraph.pageY = utils.pageY;Dygraph.dateString_ = utils.dateString_;Dygraph.defaultInteractionModel = _dygraphInteractionModel2['default'].defaultModel;Dygraph.nonInteractiveModel = Dygraph.nonInteractiveModel_ = _dygraphInteractionModel2['default'].nonInteractiveModel_;Dygraph.Circles = utils.Circles;Dygraph.Plugins = {Legend:_pluginsLegend2['default'],Axes:_pluginsAxes2['default'],Annotations:_pluginsAnnotations2['default'],ChartLabels:_pluginsChartLabels2['default'],Grid:_pluginsGrid2['default'],RangeSelector:_pluginsRangeSelector2['default']};Dygraph.DataHandlers = {DefaultHandler:_datahandlerDefault2['default'],BarsHandler:_datahandlerBars2['default'],CustomBarsHandler:_datahandlerBarsCustom2['default'],DefaultFractionHandler:_datahandlerDefaultFractions2['default'],ErrorBarsHandler:_datahandlerBarsError2['default'],FractionsBarsHandler:_datahandlerBarsFractions2['default']};Dygraph.startPan = _dygraphInteractionModel2['default'].startPan;Dygraph.startZoom = _dygraphInteractionModel2['default'].startZoom;Dygraph.movePan = _dygraphInteractionModel2['default'].movePan;Dygraph.moveZoom = _dygraphInteractionModel2['default'].moveZoom;Dygraph.endPan = _dygraphInteractionModel2['default'].endPan;Dygraph.endZoom = _dygraphInteractionModel2['default'].endZoom;Dygraph.numericLinearTicks = DygraphTickers.numericLinearTicks;Dygraph.numericTicks = DygraphTickers.numericTicks;Dygraph.dateTicker = DygraphTickers.dateTicker;Dygraph.Granularity = DygraphTickers.Granularity;Dygraph.getDateAxis = DygraphTickers.getDateAxis;Dygraph.floatFormat = utils.floatFormat;exports['default'] = Dygraph;module.exports = exports['default']; - -}).call(this,require('_process')) - -},{"./datahandler/bars":5,"./datahandler/bars-custom":2,"./datahandler/bars-error":3,"./datahandler/bars-fractions":4,"./datahandler/default":8,"./datahandler/default-fractions":7,"./dygraph-canvas":9,"./dygraph-default-attrs":10,"./dygraph-gviz":11,"./dygraph-interaction-model":12,"./dygraph-layout":13,"./dygraph-options":15,"./dygraph-options-reference":14,"./dygraph-tickers":16,"./dygraph-utils":17,"./iframe-tarp":19,"./plugins/annotations":20,"./plugins/axes":21,"./plugins/chart-labels":22,"./plugins/grid":23,"./plugins/legend":24,"./plugins/range-selector":25,"_process":1}],19:[function(require,module,exports){ -/** - * To create a "drag" interaction, you typically register a mousedown event - * handler on the element where the drag begins. In that handler, you register a - * mouseup handler on the window to determine when the mouse is released, - * wherever that release happens. This works well, except when the user releases - * the mouse over an off-domain iframe. In that case, the mouseup event is - * handled by the iframe and never bubbles up to the window handler. - * - * To deal with this issue, we cover iframes with high z-index divs to make sure - * they don't capture mouseup. - * - * Usage: - * element.addEventListener('mousedown', function() { - * var tarper = new IFrameTarp(); - * tarper.cover(); - * var mouseUpHandler = function() { - * ... - * window.removeEventListener(mouseUpHandler); - * tarper.uncover(); - * }; - * window.addEventListener('mouseup', mouseUpHandler); - * }; - * - * @constructor - */ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj["default"] = obj; return newObj; } } - -var _dygraphUtils = require('./dygraph-utils'); - -var utils = _interopRequireWildcard(_dygraphUtils); - -function IFrameTarp() { - /** @type {Array.} */ - this.tarps = []; -}; - -/** - * Find all the iframes in the document and cover them with high z-index - * transparent divs. - */ -IFrameTarp.prototype.cover = function () { - var iframes = document.getElementsByTagName("iframe"); - for (var i = 0; i < iframes.length; i++) { - var iframe = iframes[i]; - var pos = utils.findPos(iframe), - x = pos.x, - y = pos.y, - width = iframe.offsetWidth, - height = iframe.offsetHeight; - - var div = document.createElement("div"); - div.style.position = "absolute"; - div.style.left = x + 'px'; - div.style.top = y + 'px'; - div.style.width = width + 'px'; - div.style.height = height + 'px'; - div.style.zIndex = 999; - document.body.appendChild(div); - this.tarps.push(div); - } -}; - -/** - * Remove all the iframe covers. You should call this in a mouseup handler. - */ -IFrameTarp.prototype.uncover = function () { - for (var i = 0; i < this.tarps.length; i++) { - this.tarps[i].parentNode.removeChild(this.tarps[i]); - } - this.tarps = []; -}; - -exports["default"] = IFrameTarp; -module.exports = exports["default"]; - -},{"./dygraph-utils":17}],20:[function(require,module,exports){ -/** - * @license - * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/*global Dygraph:false */ - -"use strict"; - -/** -Current bits of jankiness: -- Uses dygraph.layout_ to get the parsed annotations. -- Uses dygraph.plotter_.area - -It would be nice if the plugin didn't require so much special support inside -the core dygraphs classes, but annotations involve quite a bit of parsing and -layout. - -TODO(danvk): cache DOM elements. -*/ - -Object.defineProperty(exports, "__esModule", { - value: true -}); -var annotations = function annotations() { - this.annotations_ = []; -}; - -annotations.prototype.toString = function () { - return "Annotations Plugin"; -}; - -annotations.prototype.activate = function (g) { - return { - clearChart: this.clearChart, - didDrawChart: this.didDrawChart - }; -}; - -annotations.prototype.detachLabels = function () { - for (var i = 0; i < this.annotations_.length; i++) { - var a = this.annotations_[i]; - if (a.parentNode) a.parentNode.removeChild(a); - this.annotations_[i] = null; - } - this.annotations_ = []; -}; - -annotations.prototype.clearChart = function (e) { - this.detachLabels(); -}; - -annotations.prototype.didDrawChart = function (e) { - var g = e.dygraph; - - // Early out in the (common) case of zero annotations. - var points = g.layout_.annotated_points; - if (!points || points.length === 0) return; - - var containerDiv = e.canvas.parentNode; - - var bindEvt = function bindEvt(eventName, classEventName, pt) { - return function (annotation_event) { - var a = pt.annotation; - if (a.hasOwnProperty(eventName)) { - a[eventName](a, pt, g, annotation_event); - } else if (g.getOption(classEventName)) { - g.getOption(classEventName)(a, pt, g, annotation_event); - } - }; - }; - - // Add the annotations one-by-one. - var area = e.dygraph.getArea(); - - // x-coord to sum of previous annotation's heights (used for stacking). - var xToUsedHeight = {}; - - for (var i = 0; i < points.length; i++) { - var p = points[i]; - if (p.canvasx < area.x || p.canvasx > area.x + area.w || p.canvasy < area.y || p.canvasy > area.y + area.h) { - continue; - } - - var a = p.annotation; - var tick_height = 6; - if (a.hasOwnProperty("tickHeight")) { - tick_height = a.tickHeight; - } - - // TODO: deprecate axisLabelFontSize in favor of CSS - var div = document.createElement("div"); - div.style['fontSize'] = g.getOption('axisLabelFontSize') + "px"; - var className = 'dygraph-annotation'; - if (!a.hasOwnProperty('icon')) { - // camelCase class names are deprecated. - className += ' dygraphDefaultAnnotation dygraph-default-annotation'; - } - if (a.hasOwnProperty('cssClass')) { - className += " " + a.cssClass; - } - div.className = className; - - var width = a.hasOwnProperty('width') ? a.width : 16; - var height = a.hasOwnProperty('height') ? a.height : 16; - if (a.hasOwnProperty('icon')) { - var img = document.createElement("img"); - img.src = a.icon; - img.width = width; - img.height = height; - div.appendChild(img); - } else if (p.annotation.hasOwnProperty('shortText')) { - div.appendChild(document.createTextNode(p.annotation.shortText)); - } - var left = p.canvasx - width / 2; - div.style.left = left + "px"; - var divTop = 0; - if (a.attachAtBottom) { - var y = area.y + area.h - height - tick_height; - if (xToUsedHeight[left]) { - y -= xToUsedHeight[left]; - } else { - xToUsedHeight[left] = 0; - } - xToUsedHeight[left] += tick_height + height; - divTop = y; - } else { - divTop = p.canvasy - height - tick_height; - } - div.style.top = divTop + "px"; - div.style.width = width + "px"; - div.style.height = height + "px"; - div.title = p.annotation.text; - div.style.color = g.colorsMap_[p.name]; - div.style.borderColor = g.colorsMap_[p.name]; - a.div = div; - - g.addAndTrackEvent(div, 'click', bindEvt('clickHandler', 'annotationClickHandler', p, this)); - g.addAndTrackEvent(div, 'mouseover', bindEvt('mouseOverHandler', 'annotationMouseOverHandler', p, this)); - g.addAndTrackEvent(div, 'mouseout', bindEvt('mouseOutHandler', 'annotationMouseOutHandler', p, this)); - g.addAndTrackEvent(div, 'dblclick', bindEvt('dblClickHandler', 'annotationDblClickHandler', p, this)); - - containerDiv.appendChild(div); - this.annotations_.push(div); - - var ctx = e.drawingContext; - ctx.save(); - ctx.strokeStyle = a.hasOwnProperty('tickColor') ? a.tickColor : g.colorsMap_[p.name]; - ctx.lineWidth = a.hasOwnProperty('tickWidth') ? a.tickWidth : g.getOption('strokeWidth'); - ctx.beginPath(); - if (!a.attachAtBottom) { - ctx.moveTo(p.canvasx, p.canvasy); - ctx.lineTo(p.canvasx, p.canvasy - 2 - tick_height); - } else { - var y = divTop + height; - ctx.moveTo(p.canvasx, y); - ctx.lineTo(p.canvasx, y + tick_height); - } - ctx.closePath(); - ctx.stroke(); - ctx.restore(); - } -}; - -annotations.prototype.destroy = function () { - this.detachLabels(); -}; - -exports["default"] = annotations; -module.exports = exports["default"]; - -},{}],21:[function(require,module,exports){ -/** - * @license - * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/*global Dygraph:false */ - -'use strict'; - -/* -Bits of jankiness: -- Direct layout access -- Direct area access -- Should include calculation of ticks, not just the drawing. - -Options left to make axis-friendly. - ('drawAxesAtZero') - ('xAxisHeight') -*/ - -Object.defineProperty(exports, '__esModule', { - value: true -}); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -var _dygraphUtils = require('../dygraph-utils'); - -var utils = _interopRequireWildcard(_dygraphUtils); - -/** - * Draws the axes. This includes the labels on the x- and y-axes, as well - * as the tick marks on the axes. - * It does _not_ draw the grid lines which span the entire chart. - */ -var axes = function axes() { - this.xlabels_ = []; - this.ylabels_ = []; -}; - -axes.prototype.toString = function () { - return 'Axes Plugin'; -}; - -axes.prototype.activate = function (g) { - return { - layout: this.layout, - clearChart: this.clearChart, - willDrawChart: this.willDrawChart - }; -}; - -axes.prototype.layout = function (e) { - var g = e.dygraph; - - if (g.getOptionForAxis('drawAxis', 'y')) { - var w = g.getOptionForAxis('axisLabelWidth', 'y') + 2 * g.getOptionForAxis('axisTickSize', 'y'); - e.reserveSpaceLeft(w); - } - - if (g.getOptionForAxis('drawAxis', 'x')) { - var h; - // NOTE: I think this is probably broken now, since g.getOption() now - // hits the dictionary. (That is, g.getOption('xAxisHeight') now always - // has a value.) - if (g.getOption('xAxisHeight')) { - h = g.getOption('xAxisHeight'); - } else { - h = g.getOptionForAxis('axisLabelFontSize', 'x') + 2 * g.getOptionForAxis('axisTickSize', 'x'); - } - e.reserveSpaceBottom(h); - } - - if (g.numAxes() == 2) { - if (g.getOptionForAxis('drawAxis', 'y2')) { - var w = g.getOptionForAxis('axisLabelWidth', 'y2') + 2 * g.getOptionForAxis('axisTickSize', 'y2'); - e.reserveSpaceRight(w); - } - } else if (g.numAxes() > 2) { - g.error('Only two y-axes are supported at this time. (Trying ' + 'to use ' + g.numAxes() + ')'); - } -}; - -axes.prototype.detachLabels = function () { - function removeArray(ary) { - for (var i = 0; i < ary.length; i++) { - var el = ary[i]; - if (el.parentNode) el.parentNode.removeChild(el); - } - } - - removeArray(this.xlabels_); - removeArray(this.ylabels_); - this.xlabels_ = []; - this.ylabels_ = []; -}; - -axes.prototype.clearChart = function (e) { - this.detachLabels(); -}; - -axes.prototype.willDrawChart = function (e) { - var _this = this; - - var g = e.dygraph; - - if (!g.getOptionForAxis('drawAxis', 'x') && !g.getOptionForAxis('drawAxis', 'y') && !g.getOptionForAxis('drawAxis', 'y2')) { - return; - } - - // Round pixels to half-integer boundaries for crisper drawing. - function halfUp(x) { - return Math.round(x) + 0.5; - } - function halfDown(y) { - return Math.round(y) - 0.5; - } - - var context = e.drawingContext; - var containerDiv = e.canvas.parentNode; - var canvasWidth = g.width_; // e.canvas.width is affected by pixel ratio. - var canvasHeight = g.height_; - - var label, x, y, tick, i; - - var makeLabelStyle = function makeLabelStyle(axis) { - return { - position: 'absolute', - fontSize: g.getOptionForAxis('axisLabelFontSize', axis) + 'px', - width: g.getOptionForAxis('axisLabelWidth', axis) + 'px' - }; - }; - - var labelStyles = { - x: makeLabelStyle('x'), - y: makeLabelStyle('y'), - y2: makeLabelStyle('y2') - }; - - var makeDiv = function makeDiv(txt, axis, prec_axis) { - /* - * This seems to be called with the following three sets of axis/prec_axis: - * x: undefined - * y: y1 - * y: y2 - */ - var div = document.createElement('div'); - var labelStyle = labelStyles[prec_axis == 'y2' ? 'y2' : axis]; - utils.update(div.style, labelStyle); - // TODO: combine outer & inner divs - var inner_div = document.createElement('div'); - inner_div.className = 'dygraph-axis-label' + ' dygraph-axis-label-' + axis + (prec_axis ? ' dygraph-axis-label-' + prec_axis : ''); - inner_div.innerHTML = txt; - div.appendChild(inner_div); - return div; - }; - - // axis lines - context.save(); - - var layout = g.layout_; - var area = e.dygraph.plotter_.area; - - // Helper for repeated axis-option accesses. - var makeOptionGetter = function makeOptionGetter(axis) { - return function (option) { - return g.getOptionForAxis(option, axis); - }; - }; - - if (g.getOptionForAxis('drawAxis', 'y')) { - if (layout.yticks && layout.yticks.length > 0) { - var num_axes = g.numAxes(); - var getOptions = [makeOptionGetter('y'), makeOptionGetter('y2')]; - layout.yticks.forEach(function (tick) { - if (tick.label === undefined) return; // this tick only has a grid line. - x = area.x; - var sgn = 1; - var prec_axis = 'y1'; - var getAxisOption = getOptions[0]; - if (tick.axis == 1) { - // right-side y-axis - x = area.x + area.w; - sgn = -1; - prec_axis = 'y2'; - getAxisOption = getOptions[1]; - } - var fontSize = getAxisOption('axisLabelFontSize'); - y = area.y + tick.pos * area.h; - - /* Tick marks are currently clipped, so don't bother drawing them. - context.beginPath(); - context.moveTo(halfUp(x), halfDown(y)); - context.lineTo(halfUp(x - sgn * this.attr_('axisTickSize')), halfDown(y)); - context.closePath(); - context.stroke(); - */ - - label = makeDiv(tick.label, 'y', num_axes == 2 ? prec_axis : null); - var top = y - fontSize / 2; - if (top < 0) top = 0; - - if (top + fontSize + 3 > canvasHeight) { - label.style.bottom = '0'; - } else { - label.style.top = top + 'px'; - } - // TODO: replace these with css classes? - if (tick.axis === 0) { - label.style.left = area.x - getAxisOption('axisLabelWidth') - getAxisOption('axisTickSize') + 'px'; - label.style.textAlign = 'right'; - } else if (tick.axis == 1) { - label.style.left = area.x + area.w + getAxisOption('axisTickSize') + 'px'; - label.style.textAlign = 'left'; - } - label.style.width = getAxisOption('axisLabelWidth') + 'px'; - containerDiv.appendChild(label); - _this.ylabels_.push(label); - }); - - // The lowest tick on the y-axis often overlaps with the leftmost - // tick on the x-axis. Shift the bottom tick up a little bit to - // compensate if necessary. - var bottomTick = this.ylabels_[0]; - // Interested in the y2 axis also? - var fontSize = g.getOptionForAxis('axisLabelFontSize', 'y'); - var bottom = parseInt(bottomTick.style.top, 10) + fontSize; - if (bottom > canvasHeight - fontSize) { - bottomTick.style.top = parseInt(bottomTick.style.top, 10) - fontSize / 2 + 'px'; - } - } - - // draw a vertical line on the left to separate the chart from the labels. - var axisX; - if (g.getOption('drawAxesAtZero')) { - var r = g.toPercentXCoord(0); - if (r > 1 || r < 0 || isNaN(r)) r = 0; - axisX = halfUp(area.x + r * area.w); - } else { - axisX = halfUp(area.x); - } - - context.strokeStyle = g.getOptionForAxis('axisLineColor', 'y'); - context.lineWidth = g.getOptionForAxis('axisLineWidth', 'y'); - - context.beginPath(); - context.moveTo(axisX, halfDown(area.y)); - context.lineTo(axisX, halfDown(area.y + area.h)); - context.closePath(); - context.stroke(); - - // if there's a secondary y-axis, draw a vertical line for that, too. - if (g.numAxes() == 2) { - context.strokeStyle = g.getOptionForAxis('axisLineColor', 'y2'); - context.lineWidth = g.getOptionForAxis('axisLineWidth', 'y2'); - context.beginPath(); - context.moveTo(halfDown(area.x + area.w), halfDown(area.y)); - context.lineTo(halfDown(area.x + area.w), halfDown(area.y + area.h)); - context.closePath(); - context.stroke(); - } - } - - if (g.getOptionForAxis('drawAxis', 'x')) { - if (layout.xticks) { - var getAxisOption = makeOptionGetter('x'); - layout.xticks.forEach(function (tick) { - if (tick.label === undefined) return; // this tick only has a grid line. - x = area.x + tick.pos * area.w; - y = area.y + area.h; - - /* Tick marks are currently clipped, so don't bother drawing them. - context.beginPath(); - context.moveTo(halfUp(x), halfDown(y)); - context.lineTo(halfUp(x), halfDown(y + this.attr_('axisTickSize'))); - context.closePath(); - context.stroke(); - */ - - label = makeDiv(tick.label, 'x'); - label.style.textAlign = 'center'; - label.style.top = y + getAxisOption('axisTickSize') + 'px'; - - var left = x - getAxisOption('axisLabelWidth') / 2; - if (left + getAxisOption('axisLabelWidth') > canvasWidth) { - left = canvasWidth - getAxisOption('axisLabelWidth'); - label.style.textAlign = 'right'; - } - if (left < 0) { - left = 0; - label.style.textAlign = 'left'; - } - - label.style.left = left + 'px'; - label.style.width = getAxisOption('axisLabelWidth') + 'px'; - containerDiv.appendChild(label); - _this.xlabels_.push(label); - }); - } - - context.strokeStyle = g.getOptionForAxis('axisLineColor', 'x'); - context.lineWidth = g.getOptionForAxis('axisLineWidth', 'x'); - context.beginPath(); - var axisY; - if (g.getOption('drawAxesAtZero')) { - var r = g.toPercentYCoord(0, 0); - if (r > 1 || r < 0) r = 1; - axisY = halfDown(area.y + r * area.h); - } else { - axisY = halfDown(area.y + area.h); - } - context.moveTo(halfUp(area.x), axisY); - context.lineTo(halfUp(area.x + area.w), axisY); - context.closePath(); - context.stroke(); - } - - context.restore(); -}; - -exports['default'] = axes; -module.exports = exports['default']; - -},{"../dygraph-utils":17}],22:[function(require,module,exports){ -/** - * @license - * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ -/*global Dygraph:false */ - -"use strict"; - -// TODO(danvk): move chart label options out of dygraphs and into the plugin. -// TODO(danvk): only tear down & rebuild the DIVs when it's necessary. - -Object.defineProperty(exports, "__esModule", { - value: true -}); -var chart_labels = function chart_labels() { - this.title_div_ = null; - this.xlabel_div_ = null; - this.ylabel_div_ = null; - this.y2label_div_ = null; -}; - -chart_labels.prototype.toString = function () { - return "ChartLabels Plugin"; -}; - -chart_labels.prototype.activate = function (g) { - return { - layout: this.layout, - // clearChart: this.clearChart, - didDrawChart: this.didDrawChart - }; -}; - -// QUESTION: should there be a plugin-utils.js? -var createDivInRect = function createDivInRect(r) { - var div = document.createElement('div'); - div.style.position = 'absolute'; - div.style.left = r.x + 'px'; - div.style.top = r.y + 'px'; - div.style.width = r.w + 'px'; - div.style.height = r.h + 'px'; - return div; -}; - -// Detach and null out any existing nodes. -chart_labels.prototype.detachLabels_ = function () { - var els = [this.title_div_, this.xlabel_div_, this.ylabel_div_, this.y2label_div_]; - for (var i = 0; i < els.length; i++) { - var el = els[i]; - if (!el) continue; - if (el.parentNode) el.parentNode.removeChild(el); - } - - this.title_div_ = null; - this.xlabel_div_ = null; - this.ylabel_div_ = null; - this.y2label_div_ = null; -}; - -var createRotatedDiv = function createRotatedDiv(g, box, axis, classes, html) { - // TODO(danvk): is this outer div actually necessary? - var div = document.createElement("div"); - div.style.position = 'absolute'; - if (axis == 1) { - // NOTE: this is cheating. Should be positioned relative to the box. - div.style.left = '0px'; - } else { - div.style.left = box.x + 'px'; - } - div.style.top = box.y + 'px'; - div.style.width = box.w + 'px'; - div.style.height = box.h + 'px'; - div.style.fontSize = g.getOption('yLabelWidth') - 2 + 'px'; - - var inner_div = document.createElement("div"); - inner_div.style.position = 'absolute'; - inner_div.style.width = box.h + 'px'; - inner_div.style.height = box.w + 'px'; - inner_div.style.top = box.h / 2 - box.w / 2 + 'px'; - inner_div.style.left = box.w / 2 - box.h / 2 + 'px'; - // TODO: combine inner_div and class_div. - inner_div.className = 'dygraph-label-rotate-' + (axis == 1 ? 'right' : 'left'); - - var class_div = document.createElement("div"); - class_div.className = classes; - class_div.innerHTML = html; - - inner_div.appendChild(class_div); - div.appendChild(inner_div); - return div; -}; - -chart_labels.prototype.layout = function (e) { - this.detachLabels_(); - - var g = e.dygraph; - var div = e.chart_div; - if (g.getOption('title')) { - // QUESTION: should this return an absolutely-positioned div instead? - var title_rect = e.reserveSpaceTop(g.getOption('titleHeight')); - this.title_div_ = createDivInRect(title_rect); - this.title_div_.style.fontSize = g.getOption('titleHeight') - 8 + 'px'; - - var class_div = document.createElement("div"); - class_div.className = 'dygraph-label dygraph-title'; - class_div.innerHTML = g.getOption('title'); - this.title_div_.appendChild(class_div); - div.appendChild(this.title_div_); - } - - if (g.getOption('xlabel')) { - var x_rect = e.reserveSpaceBottom(g.getOption('xLabelHeight')); - this.xlabel_div_ = createDivInRect(x_rect); - this.xlabel_div_.style.fontSize = g.getOption('xLabelHeight') - 2 + 'px'; - - var class_div = document.createElement("div"); - class_div.className = 'dygraph-label dygraph-xlabel'; - class_div.innerHTML = g.getOption('xlabel'); - this.xlabel_div_.appendChild(class_div); - div.appendChild(this.xlabel_div_); - } - - if (g.getOption('ylabel')) { - // It would make sense to shift the chart here to make room for the y-axis - // label, but the default yAxisLabelWidth is large enough that this results - // in overly-padded charts. The y-axis label should fit fine. If it - // doesn't, the yAxisLabelWidth option can be increased. - var y_rect = e.reserveSpaceLeft(0); - - this.ylabel_div_ = createRotatedDiv(g, y_rect, 1, // primary (left) y-axis - 'dygraph-label dygraph-ylabel', g.getOption('ylabel')); - div.appendChild(this.ylabel_div_); - } - - if (g.getOption('y2label') && g.numAxes() == 2) { - // same logic applies here as for ylabel. - var y2_rect = e.reserveSpaceRight(0); - this.y2label_div_ = createRotatedDiv(g, y2_rect, 2, // secondary (right) y-axis - 'dygraph-label dygraph-y2label', g.getOption('y2label')); - div.appendChild(this.y2label_div_); - } -}; - -chart_labels.prototype.didDrawChart = function (e) { - var g = e.dygraph; - if (this.title_div_) { - this.title_div_.children[0].innerHTML = g.getOption('title'); - } - if (this.xlabel_div_) { - this.xlabel_div_.children[0].innerHTML = g.getOption('xlabel'); - } - if (this.ylabel_div_) { - this.ylabel_div_.children[0].children[0].innerHTML = g.getOption('ylabel'); - } - if (this.y2label_div_) { - this.y2label_div_.children[0].children[0].innerHTML = g.getOption('y2label'); - } -}; - -chart_labels.prototype.clearChart = function () {}; - -chart_labels.prototype.destroy = function () { - this.detachLabels_(); -}; - -exports["default"] = chart_labels; -module.exports = exports["default"]; - -},{}],23:[function(require,module,exports){ -/** - * @license - * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ -/*global Dygraph:false */ - -/* - -Current bits of jankiness: -- Direct layout access -- Direct area access - -*/ - -"use strict"; - -/** - * Draws the gridlines, i.e. the gray horizontal & vertical lines running the - * length of the chart. - * - * @constructor - */ -Object.defineProperty(exports, "__esModule", { - value: true -}); -var grid = function grid() {}; - -grid.prototype.toString = function () { - return "Gridline Plugin"; -}; - -grid.prototype.activate = function (g) { - return { - willDrawChart: this.willDrawChart - }; -}; - -grid.prototype.willDrawChart = function (e) { - // Draw the new X/Y grid. Lines appear crisper when pixels are rounded to - // half-integers. This prevents them from drawing in two rows/cols. - var g = e.dygraph; - var ctx = e.drawingContext; - var layout = g.layout_; - var area = e.dygraph.plotter_.area; - - function halfUp(x) { - return Math.round(x) + 0.5; - } - function halfDown(y) { - return Math.round(y) - 0.5; - } - - var x, y, i, ticks; - if (g.getOptionForAxis('drawGrid', 'y')) { - var axes = ["y", "y2"]; - var strokeStyles = [], - lineWidths = [], - drawGrid = [], - stroking = [], - strokePattern = []; - for (var i = 0; i < axes.length; i++) { - drawGrid[i] = g.getOptionForAxis('drawGrid', axes[i]); - if (drawGrid[i]) { - strokeStyles[i] = g.getOptionForAxis('gridLineColor', axes[i]); - lineWidths[i] = g.getOptionForAxis('gridLineWidth', axes[i]); - strokePattern[i] = g.getOptionForAxis('gridLinePattern', axes[i]); - stroking[i] = strokePattern[i] && strokePattern[i].length >= 2; - } - } - ticks = layout.yticks; - ctx.save(); - // draw grids for the different y axes - ticks.forEach(function (tick) { - if (!tick.has_tick) return; - var axis = tick.axis; - if (drawGrid[axis]) { - ctx.save(); - if (stroking[axis]) { - if (ctx.setLineDash) ctx.setLineDash(strokePattern[axis]); - } - ctx.strokeStyle = strokeStyles[axis]; - ctx.lineWidth = lineWidths[axis]; - - x = halfUp(area.x); - y = halfDown(area.y + tick.pos * area.h); - ctx.beginPath(); - ctx.moveTo(x, y); - ctx.lineTo(x + area.w, y); - ctx.stroke(); - - ctx.restore(); - } - }); - ctx.restore(); - } - - // draw grid for x axis - if (g.getOptionForAxis('drawGrid', 'x')) { - ticks = layout.xticks; - ctx.save(); - var strokePattern = g.getOptionForAxis('gridLinePattern', 'x'); - var stroking = strokePattern && strokePattern.length >= 2; - if (stroking) { - if (ctx.setLineDash) ctx.setLineDash(strokePattern); - } - ctx.strokeStyle = g.getOptionForAxis('gridLineColor', 'x'); - ctx.lineWidth = g.getOptionForAxis('gridLineWidth', 'x'); - ticks.forEach(function (tick) { - if (!tick.has_tick) return; - x = halfUp(area.x + tick.pos * area.w); - y = halfDown(area.y + area.h); - ctx.beginPath(); - ctx.moveTo(x, y); - ctx.lineTo(x, area.y); - ctx.closePath(); - ctx.stroke(); - }); - if (stroking) { - if (ctx.setLineDash) ctx.setLineDash([]); - } - ctx.restore(); - } -}; - -grid.prototype.destroy = function () {}; - -exports["default"] = grid; -module.exports = exports["default"]; - -},{}],24:[function(require,module,exports){ -/** - * @license - * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ -/*global Dygraph:false */ - -/* -Current bits of jankiness: -- Uses two private APIs: - 1. Dygraph.optionsViewForAxis_ - 2. dygraph.plotter_.area -- Registers for a "predraw" event, which should be renamed. -- I call calculateEmWidthInDiv more often than needed. -*/ - -/*global Dygraph:false */ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj["default"] = obj; return newObj; } } - -var _dygraphUtils = require('../dygraph-utils'); - -var utils = _interopRequireWildcard(_dygraphUtils); - -/** - * Creates the legend, which appears when the user hovers over the chart. - * The legend can be either a user-specified or generated div. - * - * @constructor - */ -var Legend = function Legend() { - this.legend_div_ = null; - this.is_generated_div_ = false; // do we own this div, or was it user-specified? -}; - -Legend.prototype.toString = function () { - return "Legend Plugin"; -}; - -/** - * This is called during the dygraph constructor, after options have been set - * but before the data is available. - * - * Proper tasks to do here include: - * - Reading your own options - * - DOM manipulation - * - Registering event listeners - * - * @param {Dygraph} g Graph instance. - * @return {object.} Mapping of event names to callbacks. - */ -Legend.prototype.activate = function (g) { - var div; - - var userLabelsDiv = g.getOption('labelsDiv'); - if (userLabelsDiv && null !== userLabelsDiv) { - if (typeof userLabelsDiv == "string" || userLabelsDiv instanceof String) { - div = document.getElementById(userLabelsDiv); - } else { - div = userLabelsDiv; - } - } else { - div = document.createElement("div"); - div.className = "dygraph-legend"; - // TODO(danvk): come up with a cleaner way to expose this. - g.graphDiv.appendChild(div); - this.is_generated_div_ = true; - } - - this.legend_div_ = div; - this.one_em_width_ = 10; // just a guess, will be updated. - - return { - select: this.select, - deselect: this.deselect, - // TODO(danvk): rethink the name "predraw" before we commit to it in any API. - predraw: this.predraw, - didDrawChart: this.didDrawChart - }; -}; - -// Needed for dashed lines. -var calculateEmWidthInDiv = function calculateEmWidthInDiv(div) { - var sizeSpan = document.createElement('span'); - sizeSpan.setAttribute('style', 'margin: 0; padding: 0 0 0 1em; border: 0;'); - div.appendChild(sizeSpan); - var oneEmWidth = sizeSpan.offsetWidth; - div.removeChild(sizeSpan); - return oneEmWidth; -}; - -var escapeHTML = function escapeHTML(str) { - return str.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); -}; - -Legend.prototype.select = function (e) { - var xValue = e.selectedX; - var points = e.selectedPoints; - var row = e.selectedRow; - - var legendMode = e.dygraph.getOption('legend'); - if (legendMode === 'never') { - this.legend_div_.style.display = 'none'; - return; - } - - if (legendMode === 'follow') { - // create floating legend div - var area = e.dygraph.plotter_.area; - var labelsDivWidth = this.legend_div_.offsetWidth; - var yAxisLabelWidth = e.dygraph.getOptionForAxis('axisLabelWidth', 'y'); - // determine floating [left, top] coordinates of the legend div - // within the plotter_ area - // offset 50 px to the right and down from the first selection point - // 50 px is guess based on mouse cursor size - var leftLegend = points[0].x * area.w + 50; - var topLegend = points[0].y * area.h - 50; - - // if legend floats to end of the chart area, it flips to the other - // side of the selection point - if (leftLegend + labelsDivWidth + 1 > area.w) { - leftLegend = leftLegend - 2 * 50 - labelsDivWidth - (yAxisLabelWidth - area.x); - } - - e.dygraph.graphDiv.appendChild(this.legend_div_); - this.legend_div_.style.left = yAxisLabelWidth + leftLegend + "px"; - this.legend_div_.style.top = topLegend + "px"; - } - - var html = Legend.generateLegendHTML(e.dygraph, xValue, points, this.one_em_width_, row); - this.legend_div_.innerHTML = html; - this.legend_div_.style.display = ''; -}; - -Legend.prototype.deselect = function (e) { - var legendMode = e.dygraph.getOption('legend'); - if (legendMode !== 'always') { - this.legend_div_.style.display = "none"; - } - - // Have to do this every time, since styles might have changed. - var oneEmWidth = calculateEmWidthInDiv(this.legend_div_); - this.one_em_width_ = oneEmWidth; - - var html = Legend.generateLegendHTML(e.dygraph, undefined, undefined, oneEmWidth, null); - this.legend_div_.innerHTML = html; -}; - -Legend.prototype.didDrawChart = function (e) { - this.deselect(e); -}; - -// Right edge should be flush with the right edge of the charting area (which -// may not be the same as the right edge of the div, if we have two y-axes. -// TODO(danvk): is any of this really necessary? Could just set "right" in "activate". -/** - * Position the labels div so that: - * - its right edge is flush with the right edge of the charting area - * - its top edge is flush with the top edge of the charting area - * @private - */ -Legend.prototype.predraw = function (e) { - // Don't touch a user-specified labelsDiv. - if (!this.is_generated_div_) return; - - // TODO(danvk): only use real APIs for this. - e.dygraph.graphDiv.appendChild(this.legend_div_); - var area = e.dygraph.getArea(); - var labelsDivWidth = this.legend_div_.offsetWidth; - this.legend_div_.style.left = area.x + area.w - labelsDivWidth - 1 + "px"; - this.legend_div_.style.top = area.y + "px"; -}; - -/** - * Called when dygraph.destroy() is called. - * You should null out any references and detach any DOM elements. - */ -Legend.prototype.destroy = function () { - this.legend_div_ = null; -}; - -/** - * Generates HTML for the legend which is displayed when hovering over the - * chart. If no selected points are specified, a default legend is returned - * (this may just be the empty string). - * @param {number} x The x-value of the selected points. - * @param {Object} sel_points List of selected points for the given - * x-value. Should have properties like 'name', 'yval' and 'canvasy'. - * @param {number} oneEmWidth The pixel width for 1em in the legend. Only - * relevant when displaying a legend with no selection (i.e. {legend: - * 'always'}) and with dashed lines. - * @param {number} row The selected row index. - * @private - */ -Legend.generateLegendHTML = function (g, x, sel_points, oneEmWidth, row) { - // Data about the selection to pass to legendFormatter - var data = { - dygraph: g, - x: x, - series: [] - }; - - var labelToSeries = {}; - var labels = g.getLabels(); - if (labels) { - for (var i = 1; i < labels.length; i++) { - var series = g.getPropertiesForSeries(labels[i]); - var strokePattern = g.getOption('strokePattern', labels[i]); - var seriesData = { - dashHTML: generateLegendDashHTML(strokePattern, series.color, oneEmWidth), - label: labels[i], - labelHTML: escapeHTML(labels[i]), - isVisible: series.visible, - color: series.color - }; - - data.series.push(seriesData); - labelToSeries[labels[i]] = seriesData; - } - } - - if (typeof x !== 'undefined') { - var xOptView = g.optionsViewForAxis_('x'); - var xvf = xOptView('valueFormatter'); - data.xHTML = xvf.call(g, x, xOptView, labels[0], g, row, 0); - - var yOptViews = []; - var num_axes = g.numAxes(); - for (var i = 0; i < num_axes; i++) { - // TODO(danvk): remove this use of a private API - yOptViews[i] = g.optionsViewForAxis_('y' + (i ? 1 + i : '')); - } - - var showZeros = g.getOption('labelsShowZeroValues'); - var highlightSeries = g.getHighlightSeries(); - for (i = 0; i < sel_points.length; i++) { - var pt = sel_points[i]; - var seriesData = labelToSeries[pt.name]; - seriesData.y = pt.yval; - - if (pt.yval === 0 && !showZeros || isNaN(pt.canvasy)) { - seriesData.isVisible = false; - continue; - } - - var series = g.getPropertiesForSeries(pt.name); - var yOptView = yOptViews[series.axis - 1]; - var fmtFunc = yOptView('valueFormatter'); - var yHTML = fmtFunc.call(g, pt.yval, yOptView, pt.name, g, row, labels.indexOf(pt.name)); - - utils.update(seriesData, { yHTML: yHTML }); - - if (pt.name == highlightSeries) { - seriesData.isHighlighted = true; - } - } - } - - var formatter = g.getOption('legendFormatter') || Legend.defaultFormatter; - return formatter.call(g, data); -}; - -Legend.defaultFormatter = function (data) { - var g = data.dygraph; - - // TODO(danvk): deprecate this option in place of {legend: 'never'} - // XXX should this logic be in the formatter? - if (g.getOption('showLabelsOnHighlight') !== true) return ''; - - var sepLines = g.getOption('labelsSeparateLines'); - var html; - - if (typeof data.x === 'undefined') { - // TODO: this check is duplicated in generateLegendHTML. Put it in one place. - if (g.getOption('legend') != 'always') { - return ''; - } - - html = ''; - for (var i = 0; i < data.series.length; i++) { - var series = data.series[i]; - if (!series.isVisible) continue; - - if (html !== '') html += sepLines ? '
' : ' '; - html += "" + series.dashHTML + " " + series.labelHTML + ""; - } - return html; - } - - html = data.xHTML + ':'; - for (var i = 0; i < data.series.length; i++) { - var series = data.series[i]; - if (!series.isVisible) continue; - if (sepLines) html += '
'; - var cls = series.isHighlighted ? ' class="highlight"' : ''; - html += " " + series.labelHTML + ": " + series.yHTML + ""; - } - return html; -}; - -/** - * Generates html for the "dash" displayed on the legend when using "legend: always". - * In particular, this works for dashed lines with any stroke pattern. It will - * try to scale the pattern to fit in 1em width. Or if small enough repeat the - * pattern for 1em width. - * - * @param strokePattern The pattern - * @param color The color of the series. - * @param oneEmWidth The width in pixels of 1em in the legend. - * @private - */ -// TODO(danvk): cache the results of this -function generateLegendDashHTML(strokePattern, color, oneEmWidth) { - // Easy, common case: a solid line - if (!strokePattern || strokePattern.length <= 1) { - return "
"; - } - - var i, j, paddingLeft, marginRight; - var strokePixelLength = 0, - segmentLoop = 0; - var normalizedPattern = []; - var loop; - - // Compute the length of the pixels including the first segment twice, - // since we repeat it. - for (i = 0; i <= strokePattern.length; i++) { - strokePixelLength += strokePattern[i % strokePattern.length]; - } - - // See if we can loop the pattern by itself at least twice. - loop = Math.floor(oneEmWidth / (strokePixelLength - strokePattern[0])); - if (loop > 1) { - // This pattern fits at least two times, no scaling just convert to em; - for (i = 0; i < strokePattern.length; i++) { - normalizedPattern[i] = strokePattern[i] / oneEmWidth; - } - // Since we are repeating the pattern, we don't worry about repeating the - // first segment in one draw. - segmentLoop = normalizedPattern.length; - } else { - // If the pattern doesn't fit in the legend we scale it to fit. - loop = 1; - for (i = 0; i < strokePattern.length; i++) { - normalizedPattern[i] = strokePattern[i] / strokePixelLength; - } - // For the scaled patterns we do redraw the first segment. - segmentLoop = normalizedPattern.length + 1; - } - - // Now make the pattern. - var dash = ""; - for (j = 0; j < loop; j++) { - for (i = 0; i < segmentLoop; i += 2) { - // The padding is the drawn segment. - paddingLeft = normalizedPattern[i % normalizedPattern.length]; - if (i < strokePattern.length) { - // The margin is the space segment. - marginRight = normalizedPattern[(i + 1) % normalizedPattern.length]; - } else { - // The repeated first segment has no right margin. - marginRight = 0; - } - dash += "
"; - } - } - return dash; -}; - -exports["default"] = Legend; -module.exports = exports["default"]; - -},{"../dygraph-utils":17}],25:[function(require,module,exports){ -/** - * @license - * Copyright 2011 Paul Felix (paul.eric.felix@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ -/*global Dygraph:false,TouchEvent:false */ - -/** - * @fileoverview This file contains the RangeSelector plugin used to provide - * a timeline range selector widget for dygraphs. - */ - -/*global Dygraph:false */ -"use strict"; - -Object.defineProperty(exports, '__esModule', { - value: true -}); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -var _dygraphUtils = require('../dygraph-utils'); - -var utils = _interopRequireWildcard(_dygraphUtils); - -var _dygraphInteractionModel = require('../dygraph-interaction-model'); - -var _dygraphInteractionModel2 = _interopRequireDefault(_dygraphInteractionModel); - -var _iframeTarp = require('../iframe-tarp'); - -var _iframeTarp2 = _interopRequireDefault(_iframeTarp); - -var rangeSelector = function rangeSelector() { - this.hasTouchInterface_ = typeof TouchEvent != 'undefined'; - this.isMobileDevice_ = /mobile|android/gi.test(navigator.appVersion); - this.interfaceCreated_ = false; -}; - -rangeSelector.prototype.toString = function () { - return "RangeSelector Plugin"; -}; - -rangeSelector.prototype.activate = function (dygraph) { - this.dygraph_ = dygraph; - if (this.getOption_('showRangeSelector')) { - this.createInterface_(); - } - return { - layout: this.reserveSpace_, - predraw: this.renderStaticLayer_, - didDrawChart: this.renderInteractiveLayer_ - }; -}; - -rangeSelector.prototype.destroy = function () { - this.bgcanvas_ = null; - this.fgcanvas_ = null; - this.leftZoomHandle_ = null; - this.rightZoomHandle_ = null; -}; - -//------------------------------------------------------------------ -// Private methods -//------------------------------------------------------------------ - -rangeSelector.prototype.getOption_ = function (name, opt_series) { - return this.dygraph_.getOption(name, opt_series); -}; - -rangeSelector.prototype.setDefaultOption_ = function (name, value) { - this.dygraph_.attrs_[name] = value; -}; - -/** - * @private - * Creates the range selector elements and adds them to the graph. - */ -rangeSelector.prototype.createInterface_ = function () { - this.createCanvases_(); - this.createZoomHandles_(); - this.initInteraction_(); - - // Range selector and animatedZooms have a bad interaction. See issue 359. - if (this.getOption_('animatedZooms')) { - console.warn('Animated zooms and range selector are not compatible; disabling animatedZooms.'); - this.dygraph_.updateOptions({ animatedZooms: false }, true); - } - - this.interfaceCreated_ = true; - this.addToGraph_(); -}; - -/** - * @private - * Adds the range selector to the graph. - */ -rangeSelector.prototype.addToGraph_ = function () { - var graphDiv = this.graphDiv_ = this.dygraph_.graphDiv; - graphDiv.appendChild(this.bgcanvas_); - graphDiv.appendChild(this.fgcanvas_); - graphDiv.appendChild(this.leftZoomHandle_); - graphDiv.appendChild(this.rightZoomHandle_); -}; - -/** - * @private - * Removes the range selector from the graph. - */ -rangeSelector.prototype.removeFromGraph_ = function () { - var graphDiv = this.graphDiv_; - graphDiv.removeChild(this.bgcanvas_); - graphDiv.removeChild(this.fgcanvas_); - graphDiv.removeChild(this.leftZoomHandle_); - graphDiv.removeChild(this.rightZoomHandle_); - this.graphDiv_ = null; -}; - -/** - * @private - * Called by Layout to allow range selector to reserve its space. - */ -rangeSelector.prototype.reserveSpace_ = function (e) { - if (this.getOption_('showRangeSelector')) { - e.reserveSpaceBottom(this.getOption_('rangeSelectorHeight') + 4); - } -}; - -/** - * @private - * Renders the static portion of the range selector at the predraw stage. - */ -rangeSelector.prototype.renderStaticLayer_ = function () { - if (!this.updateVisibility_()) { - return; - } - this.resize_(); - this.drawStaticLayer_(); -}; - -/** - * @private - * Renders the interactive portion of the range selector after the chart has been drawn. - */ -rangeSelector.prototype.renderInteractiveLayer_ = function () { - if (!this.updateVisibility_() || this.isChangingRange_) { - return; - } - this.placeZoomHandles_(); - this.drawInteractiveLayer_(); -}; - -/** - * @private - * Check to see if the range selector is enabled/disabled and update visibility accordingly. - */ -rangeSelector.prototype.updateVisibility_ = function () { - var enabled = this.getOption_('showRangeSelector'); - if (enabled) { - if (!this.interfaceCreated_) { - this.createInterface_(); - } else if (!this.graphDiv_ || !this.graphDiv_.parentNode) { - this.addToGraph_(); - } - } else if (this.graphDiv_) { - this.removeFromGraph_(); - var dygraph = this.dygraph_; - setTimeout(function () { - dygraph.width_ = 0;dygraph.resize(); - }, 1); - } - return enabled; -}; - -/** - * @private - * Resizes the range selector. - */ -rangeSelector.prototype.resize_ = function () { - function setElementRect(canvas, context, rect, pixelRatioOption) { - var canvasScale = pixelRatioOption || utils.getContextPixelRatio(context); - - canvas.style.top = rect.y + 'px'; - canvas.style.left = rect.x + 'px'; - canvas.width = rect.w * canvasScale; - canvas.height = rect.h * canvasScale; - canvas.style.width = rect.w + 'px'; - canvas.style.height = rect.h + 'px'; - - if (canvasScale != 1) { - context.scale(canvasScale, canvasScale); - } - } - - var plotArea = this.dygraph_.layout_.getPlotArea(); - - var xAxisLabelHeight = 0; - if (this.dygraph_.getOptionForAxis('drawAxis', 'x')) { - xAxisLabelHeight = this.getOption_('xAxisHeight') || this.getOption_('axisLabelFontSize') + 2 * this.getOption_('axisTickSize'); - } - this.canvasRect_ = { - x: plotArea.x, - y: plotArea.y + plotArea.h + xAxisLabelHeight + 4, - w: plotArea.w, - h: this.getOption_('rangeSelectorHeight') - }; - - var pixelRatioOption = this.dygraph_.getNumericOption('pixelRatio'); - setElementRect(this.bgcanvas_, this.bgcanvas_ctx_, this.canvasRect_, pixelRatioOption); - setElementRect(this.fgcanvas_, this.fgcanvas_ctx_, this.canvasRect_, pixelRatioOption); -}; - -/** - * @private - * Creates the background and foreground canvases. - */ -rangeSelector.prototype.createCanvases_ = function () { - this.bgcanvas_ = utils.createCanvas(); - this.bgcanvas_.className = 'dygraph-rangesel-bgcanvas'; - this.bgcanvas_.style.position = 'absolute'; - this.bgcanvas_.style.zIndex = 9; - this.bgcanvas_ctx_ = utils.getContext(this.bgcanvas_); - - this.fgcanvas_ = utils.createCanvas(); - this.fgcanvas_.className = 'dygraph-rangesel-fgcanvas'; - this.fgcanvas_.style.position = 'absolute'; - this.fgcanvas_.style.zIndex = 9; - this.fgcanvas_.style.cursor = 'default'; - this.fgcanvas_ctx_ = utils.getContext(this.fgcanvas_); -}; - -/** - * @private - * Creates the zoom handle elements. - */ -rangeSelector.prototype.createZoomHandles_ = function () { - var img = new Image(); - img.className = 'dygraph-rangesel-zoomhandle'; - img.style.position = 'absolute'; - img.style.zIndex = 10; - img.style.visibility = 'hidden'; // Initially hidden so they don't show up in the wrong place. - img.style.cursor = 'col-resize'; - // TODO: change image to more options - img.width = 9; - img.height = 16; - img.src = 'data:image/png;base64,' + 'iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAA' + 'zwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENv' + 'bW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl' + '6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7s' + 'qSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII='; - - if (this.isMobileDevice_) { - img.width *= 2; - img.height *= 2; - } - - this.leftZoomHandle_ = img; - this.rightZoomHandle_ = img.cloneNode(false); -}; - -/** - * @private - * Sets up the interaction for the range selector. - */ -rangeSelector.prototype.initInteraction_ = function () { - var self = this; - var topElem = document; - var clientXLast = 0; - var handle = null; - var isZooming = false; - var isPanning = false; - var dynamic = !this.isMobileDevice_; - - // We cover iframes during mouse interactions. See comments in - // dygraph-utils.js for more info on why this is a good idea. - var tarp = new _iframeTarp2['default'](); - - // functions, defined below. Defining them this way (rather than with - // "function foo() {...}" makes JSHint happy. - var toXDataWindow, onZoomStart, onZoom, onZoomEnd, doZoom, isMouseInPanZone, onPanStart, onPan, onPanEnd, doPan, onCanvasHover; - - // Touch event functions - var onZoomHandleTouchEvent, onCanvasTouchEvent, addTouchEvents; - - toXDataWindow = function (zoomHandleStatus) { - var xDataLimits = self.dygraph_.xAxisExtremes(); - var fact = (xDataLimits[1] - xDataLimits[0]) / self.canvasRect_.w; - var xDataMin = xDataLimits[0] + (zoomHandleStatus.leftHandlePos - self.canvasRect_.x) * fact; - var xDataMax = xDataLimits[0] + (zoomHandleStatus.rightHandlePos - self.canvasRect_.x) * fact; - return [xDataMin, xDataMax]; - }; - - onZoomStart = function (e) { - utils.cancelEvent(e); - isZooming = true; - clientXLast = e.clientX; - handle = e.target ? e.target : e.srcElement; - if (e.type === 'mousedown' || e.type === 'dragstart') { - // These events are removed manually. - utils.addEvent(topElem, 'mousemove', onZoom); - utils.addEvent(topElem, 'mouseup', onZoomEnd); - } - self.fgcanvas_.style.cursor = 'col-resize'; - tarp.cover(); - return true; - }; - - onZoom = function (e) { - if (!isZooming) { - return false; - } - utils.cancelEvent(e); - - var delX = e.clientX - clientXLast; - if (Math.abs(delX) < 4) { - return true; - } - clientXLast = e.clientX; - - // Move handle. - var zoomHandleStatus = self.getZoomHandleStatus_(); - var newPos; - if (handle == self.leftZoomHandle_) { - newPos = zoomHandleStatus.leftHandlePos + delX; - newPos = Math.min(newPos, zoomHandleStatus.rightHandlePos - handle.width - 3); - newPos = Math.max(newPos, self.canvasRect_.x); - } else { - newPos = zoomHandleStatus.rightHandlePos + delX; - newPos = Math.min(newPos, self.canvasRect_.x + self.canvasRect_.w); - newPos = Math.max(newPos, zoomHandleStatus.leftHandlePos + handle.width + 3); - } - var halfHandleWidth = handle.width / 2; - handle.style.left = newPos - halfHandleWidth + 'px'; - self.drawInteractiveLayer_(); - - // Zoom on the fly. - if (dynamic) { - doZoom(); - } - return true; - }; - - onZoomEnd = function (e) { - if (!isZooming) { - return false; - } - isZooming = false; - tarp.uncover(); - utils.removeEvent(topElem, 'mousemove', onZoom); - utils.removeEvent(topElem, 'mouseup', onZoomEnd); - self.fgcanvas_.style.cursor = 'default'; - - // If on a slower device, zoom now. - if (!dynamic) { - doZoom(); - } - return true; - }; - - doZoom = function () { - try { - var zoomHandleStatus = self.getZoomHandleStatus_(); - self.isChangingRange_ = true; - if (!zoomHandleStatus.isZoomed) { - self.dygraph_.resetZoom(); - } else { - var xDataWindow = toXDataWindow(zoomHandleStatus); - self.dygraph_.doZoomXDates_(xDataWindow[0], xDataWindow[1]); - } - } finally { - self.isChangingRange_ = false; - } - }; - - isMouseInPanZone = function (e) { - var rect = self.leftZoomHandle_.getBoundingClientRect(); - var leftHandleClientX = rect.left + rect.width / 2; - rect = self.rightZoomHandle_.getBoundingClientRect(); - var rightHandleClientX = rect.left + rect.width / 2; - return e.clientX > leftHandleClientX && e.clientX < rightHandleClientX; - }; - - onPanStart = function (e) { - if (!isPanning && isMouseInPanZone(e) && self.getZoomHandleStatus_().isZoomed) { - utils.cancelEvent(e); - isPanning = true; - clientXLast = e.clientX; - if (e.type === 'mousedown') { - // These events are removed manually. - utils.addEvent(topElem, 'mousemove', onPan); - utils.addEvent(topElem, 'mouseup', onPanEnd); - } - return true; - } - return false; - }; - - onPan = function (e) { - if (!isPanning) { - return false; - } - utils.cancelEvent(e); - - var delX = e.clientX - clientXLast; - if (Math.abs(delX) < 4) { - return true; - } - clientXLast = e.clientX; - - // Move range view - var zoomHandleStatus = self.getZoomHandleStatus_(); - var leftHandlePos = zoomHandleStatus.leftHandlePos; - var rightHandlePos = zoomHandleStatus.rightHandlePos; - var rangeSize = rightHandlePos - leftHandlePos; - if (leftHandlePos + delX <= self.canvasRect_.x) { - leftHandlePos = self.canvasRect_.x; - rightHandlePos = leftHandlePos + rangeSize; - } else if (rightHandlePos + delX >= self.canvasRect_.x + self.canvasRect_.w) { - rightHandlePos = self.canvasRect_.x + self.canvasRect_.w; - leftHandlePos = rightHandlePos - rangeSize; - } else { - leftHandlePos += delX; - rightHandlePos += delX; - } - var halfHandleWidth = self.leftZoomHandle_.width / 2; - self.leftZoomHandle_.style.left = leftHandlePos - halfHandleWidth + 'px'; - self.rightZoomHandle_.style.left = rightHandlePos - halfHandleWidth + 'px'; - self.drawInteractiveLayer_(); - - // Do pan on the fly. - if (dynamic) { - doPan(); - } - return true; - }; - - onPanEnd = function (e) { - if (!isPanning) { - return false; - } - isPanning = false; - utils.removeEvent(topElem, 'mousemove', onPan); - utils.removeEvent(topElem, 'mouseup', onPanEnd); - // If on a slower device, do pan now. - if (!dynamic) { - doPan(); - } - return true; - }; - - doPan = function () { - try { - self.isChangingRange_ = true; - self.dygraph_.dateWindow_ = toXDataWindow(self.getZoomHandleStatus_()); - self.dygraph_.drawGraph_(false); - } finally { - self.isChangingRange_ = false; - } - }; - - onCanvasHover = function (e) { - if (isZooming || isPanning) { - return; - } - var cursor = isMouseInPanZone(e) ? 'move' : 'default'; - if (cursor != self.fgcanvas_.style.cursor) { - self.fgcanvas_.style.cursor = cursor; - } - }; - - onZoomHandleTouchEvent = function (e) { - if (e.type == 'touchstart' && e.targetTouches.length == 1) { - if (onZoomStart(e.targetTouches[0])) { - utils.cancelEvent(e); - } - } else if (e.type == 'touchmove' && e.targetTouches.length == 1) { - if (onZoom(e.targetTouches[0])) { - utils.cancelEvent(e); - } - } else { - onZoomEnd(e); - } - }; - - onCanvasTouchEvent = function (e) { - if (e.type == 'touchstart' && e.targetTouches.length == 1) { - if (onPanStart(e.targetTouches[0])) { - utils.cancelEvent(e); - } - } else if (e.type == 'touchmove' && e.targetTouches.length == 1) { - if (onPan(e.targetTouches[0])) { - utils.cancelEvent(e); - } - } else { - onPanEnd(e); - } - }; - - addTouchEvents = function (elem, fn) { - var types = ['touchstart', 'touchend', 'touchmove', 'touchcancel']; - for (var i = 0; i < types.length; i++) { - self.dygraph_.addAndTrackEvent(elem, types[i], fn); - } - }; - - this.setDefaultOption_('interactionModel', _dygraphInteractionModel2['default'].dragIsPanInteractionModel); - this.setDefaultOption_('panEdgeFraction', 0.0001); - - var dragStartEvent = window.opera ? 'mousedown' : 'dragstart'; - this.dygraph_.addAndTrackEvent(this.leftZoomHandle_, dragStartEvent, onZoomStart); - this.dygraph_.addAndTrackEvent(this.rightZoomHandle_, dragStartEvent, onZoomStart); - - this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousedown', onPanStart); - this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousemove', onCanvasHover); - - // Touch events - if (this.hasTouchInterface_) { - addTouchEvents(this.leftZoomHandle_, onZoomHandleTouchEvent); - addTouchEvents(this.rightZoomHandle_, onZoomHandleTouchEvent); - addTouchEvents(this.fgcanvas_, onCanvasTouchEvent); - } -}; - -/** - * @private - * Draws the static layer in the background canvas. - */ -rangeSelector.prototype.drawStaticLayer_ = function () { - var ctx = this.bgcanvas_ctx_; - ctx.clearRect(0, 0, this.canvasRect_.w, this.canvasRect_.h); - try { - this.drawMiniPlot_(); - } catch (ex) { - console.warn(ex); - } - - var margin = 0.5; - this.bgcanvas_ctx_.lineWidth = this.getOption_('rangeSelectorBackgroundLineWidth'); - ctx.strokeStyle = this.getOption_('rangeSelectorBackgroundStrokeColor'); - ctx.beginPath(); - ctx.moveTo(margin, margin); - ctx.lineTo(margin, this.canvasRect_.h - margin); - ctx.lineTo(this.canvasRect_.w - margin, this.canvasRect_.h - margin); - ctx.lineTo(this.canvasRect_.w - margin, margin); - ctx.stroke(); -}; - -/** - * @private - * Draws the mini plot in the background canvas. - */ -rangeSelector.prototype.drawMiniPlot_ = function () { - var fillStyle = this.getOption_('rangeSelectorPlotFillColor'); - var fillGradientStyle = this.getOption_('rangeSelectorPlotFillGradientColor'); - var strokeStyle = this.getOption_('rangeSelectorPlotStrokeColor'); - if (!fillStyle && !strokeStyle) { - return; - } - - var stepPlot = this.getOption_('stepPlot'); - - var combinedSeriesData = this.computeCombinedSeriesAndLimits_(); - var yRange = combinedSeriesData.yMax - combinedSeriesData.yMin; - - // Draw the mini plot. - var ctx = this.bgcanvas_ctx_; - var margin = 0.5; - - var xExtremes = this.dygraph_.xAxisExtremes(); - var xRange = Math.max(xExtremes[1] - xExtremes[0], 1.e-30); - var xFact = (this.canvasRect_.w - margin) / xRange; - var yFact = (this.canvasRect_.h - margin) / yRange; - var canvasWidth = this.canvasRect_.w - margin; - var canvasHeight = this.canvasRect_.h - margin; - - var prevX = null, - prevY = null; - - ctx.beginPath(); - ctx.moveTo(margin, canvasHeight); - for (var i = 0; i < combinedSeriesData.data.length; i++) { - var dataPoint = combinedSeriesData.data[i]; - var x = dataPoint[0] !== null ? (dataPoint[0] - xExtremes[0]) * xFact : NaN; - var y = dataPoint[1] !== null ? canvasHeight - (dataPoint[1] - combinedSeriesData.yMin) * yFact : NaN; - - // Skip points that don't change the x-value. Overly fine-grained points - // can cause major slowdowns with the ctx.fill() call below. - if (!stepPlot && prevX !== null && Math.round(x) == Math.round(prevX)) { - continue; - } - - if (isFinite(x) && isFinite(y)) { - if (prevX === null) { - ctx.lineTo(x, canvasHeight); - } else if (stepPlot) { - ctx.lineTo(x, prevY); - } - ctx.lineTo(x, y); - prevX = x; - prevY = y; - } else { - if (prevX !== null) { - if (stepPlot) { - ctx.lineTo(x, prevY); - ctx.lineTo(x, canvasHeight); - } else { - ctx.lineTo(prevX, canvasHeight); - } - } - prevX = prevY = null; - } - } - ctx.lineTo(canvasWidth, canvasHeight); - ctx.closePath(); - - if (fillStyle) { - var lingrad = this.bgcanvas_ctx_.createLinearGradient(0, 0, 0, canvasHeight); - if (fillGradientStyle) { - lingrad.addColorStop(0, fillGradientStyle); - } - lingrad.addColorStop(1, fillStyle); - this.bgcanvas_ctx_.fillStyle = lingrad; - ctx.fill(); - } - - if (strokeStyle) { - this.bgcanvas_ctx_.strokeStyle = strokeStyle; - this.bgcanvas_ctx_.lineWidth = this.getOption_('rangeSelectorPlotLineWidth'); - ctx.stroke(); - } -}; - -/** - * @private - * Computes and returns the combined series data along with min/max for the mini plot. - * The combined series consists of averaged values for all series. - * When series have error bars, the error bars are ignored. - * @return {Object} An object containing combined series array, ymin, ymax. - */ -rangeSelector.prototype.computeCombinedSeriesAndLimits_ = function () { - var g = this.dygraph_; - var logscale = this.getOption_('logscale'); - var i; - - // Select series to combine. By default, all series are combined. - var numColumns = g.numColumns(); - var labels = g.getLabels(); - var includeSeries = new Array(numColumns); - var anySet = false; - var visibility = g.visibility(); - var inclusion = []; - - for (i = 1; i < numColumns; i++) { - var include = this.getOption_('showInRangeSelector', labels[i]); - inclusion.push(include); - if (include !== null) anySet = true; // it's set explicitly for this series - } - - if (anySet) { - for (i = 1; i < numColumns; i++) { - includeSeries[i] = inclusion[i - 1]; - } - } else { - for (i = 1; i < numColumns; i++) { - includeSeries[i] = visibility[i - 1]; - } - } - - // Create a combined series (average of selected series values). - // TODO(danvk): short-circuit if there's only one series. - var rolledSeries = []; - var dataHandler = g.dataHandler_; - var options = g.attributes_; - for (i = 1; i < g.numColumns(); i++) { - if (!includeSeries[i]) continue; - var series = dataHandler.extractSeries(g.rawData_, i, options); - if (g.rollPeriod() > 1) { - series = dataHandler.rollingAverage(series, g.rollPeriod(), options); - } - - rolledSeries.push(series); - } - - var combinedSeries = []; - for (i = 0; i < rolledSeries[0].length; i++) { - var sum = 0; - var count = 0; - for (var j = 0; j < rolledSeries.length; j++) { - var y = rolledSeries[j][i][1]; - if (y === null || isNaN(y)) continue; - count++; - sum += y; - } - combinedSeries.push([rolledSeries[0][i][0], sum / count]); - } - - // Compute the y range. - var yMin = Number.MAX_VALUE; - var yMax = -Number.MAX_VALUE; - for (i = 0; i < combinedSeries.length; i++) { - var yVal = combinedSeries[i][1]; - if (yVal !== null && isFinite(yVal) && (!logscale || yVal > 0)) { - yMin = Math.min(yMin, yVal); - yMax = Math.max(yMax, yVal); - } - } - - // Convert Y data to log scale if needed. - // Also, expand the Y range to compress the mini plot a little. - var extraPercent = 0.25; - if (logscale) { - yMax = utils.log10(yMax); - yMax += yMax * extraPercent; - yMin = utils.log10(yMin); - for (i = 0; i < combinedSeries.length; i++) { - combinedSeries[i][1] = utils.log10(combinedSeries[i][1]); - } - } else { - var yExtra; - var yRange = yMax - yMin; - if (yRange <= Number.MIN_VALUE) { - yExtra = yMax * extraPercent; - } else { - yExtra = yRange * extraPercent; - } - yMax += yExtra; - yMin -= yExtra; - } - - return { data: combinedSeries, yMin: yMin, yMax: yMax }; -}; - -/** - * @private - * Places the zoom handles in the proper position based on the current X data window. - */ -rangeSelector.prototype.placeZoomHandles_ = function () { - var xExtremes = this.dygraph_.xAxisExtremes(); - var xWindowLimits = this.dygraph_.xAxisRange(); - var xRange = xExtremes[1] - xExtremes[0]; - var leftPercent = Math.max(0, (xWindowLimits[0] - xExtremes[0]) / xRange); - var rightPercent = Math.max(0, (xExtremes[1] - xWindowLimits[1]) / xRange); - var leftCoord = this.canvasRect_.x + this.canvasRect_.w * leftPercent; - var rightCoord = this.canvasRect_.x + this.canvasRect_.w * (1 - rightPercent); - var handleTop = Math.max(this.canvasRect_.y, this.canvasRect_.y + (this.canvasRect_.h - this.leftZoomHandle_.height) / 2); - var halfHandleWidth = this.leftZoomHandle_.width / 2; - this.leftZoomHandle_.style.left = leftCoord - halfHandleWidth + 'px'; - this.leftZoomHandle_.style.top = handleTop + 'px'; - this.rightZoomHandle_.style.left = rightCoord - halfHandleWidth + 'px'; - this.rightZoomHandle_.style.top = this.leftZoomHandle_.style.top; - - this.leftZoomHandle_.style.visibility = 'visible'; - this.rightZoomHandle_.style.visibility = 'visible'; -}; - -/** - * @private - * Draws the interactive layer in the foreground canvas. - */ -rangeSelector.prototype.drawInteractiveLayer_ = function () { - var ctx = this.fgcanvas_ctx_; - ctx.clearRect(0, 0, this.canvasRect_.w, this.canvasRect_.h); - var margin = 1; - var width = this.canvasRect_.w - margin; - var height = this.canvasRect_.h - margin; - var zoomHandleStatus = this.getZoomHandleStatus_(); - - ctx.strokeStyle = this.getOption_('rangeSelectorForegroundStrokeColor'); - ctx.lineWidth = this.getOption_('rangeSelectorForegroundLineWidth'); - if (!zoomHandleStatus.isZoomed) { - ctx.beginPath(); - ctx.moveTo(margin, margin); - ctx.lineTo(margin, height); - ctx.lineTo(width, height); - ctx.lineTo(width, margin); - ctx.stroke(); - } else { - var leftHandleCanvasPos = Math.max(margin, zoomHandleStatus.leftHandlePos - this.canvasRect_.x); - var rightHandleCanvasPos = Math.min(width, zoomHandleStatus.rightHandlePos - this.canvasRect_.x); - - ctx.fillStyle = 'rgba(240, 240, 240, ' + this.getOption_('rangeSelectorAlpha').toString() + ')'; - ctx.fillRect(0, 0, leftHandleCanvasPos, this.canvasRect_.h); - ctx.fillRect(rightHandleCanvasPos, 0, this.canvasRect_.w - rightHandleCanvasPos, this.canvasRect_.h); - - ctx.beginPath(); - ctx.moveTo(margin, margin); - ctx.lineTo(leftHandleCanvasPos, margin); - ctx.lineTo(leftHandleCanvasPos, height); - ctx.lineTo(rightHandleCanvasPos, height); - ctx.lineTo(rightHandleCanvasPos, margin); - ctx.lineTo(width, margin); - ctx.stroke(); - } -}; - -/** - * @private - * Returns the current zoom handle position information. - * @return {Object} The zoom handle status. - */ -rangeSelector.prototype.getZoomHandleStatus_ = function () { - var halfHandleWidth = this.leftZoomHandle_.width / 2; - var leftHandlePos = parseFloat(this.leftZoomHandle_.style.left) + halfHandleWidth; - var rightHandlePos = parseFloat(this.rightZoomHandle_.style.left) + halfHandleWidth; - return { - leftHandlePos: leftHandlePos, - rightHandlePos: rightHandlePos, - isZoomed: leftHandlePos - 1 > this.canvasRect_.x || rightHandlePos + 1 < this.canvasRect_.x + this.canvasRect_.w - }; -}; - -exports['default'] = rangeSelector; -module.exports = exports['default']; - -},{"../dygraph-interaction-model":12,"../dygraph-utils":17,"../iframe-tarp":19}]},{},[18])(18) -}); -//# sourceMappingURL=dygraph.js.map diff -Nru knot-resolver-5.1.1/debian/missing-sources/dygraph.js knot-resolver-5.2.1/debian/missing-sources/dygraph.js --- knot-resolver-5.1.1/debian/missing-sources/dygraph.js 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/debian/missing-sources/dygraph.js 2020-12-14 14:45:51.000000000 +0000 @@ -0,0 +1,9464 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Dygraph = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; + +process.listeners = function (name) { return [] } + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],2:[function(require,module,exports){ +/** + * @license + * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview DataHandler implementation for the custom bars option. + * @author David Eberlein (david.eberlein@ch.sauter-bc.com) + */ + +/*global Dygraph:false */ +"use strict"; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _bars = require('./bars'); + +var _bars2 = _interopRequireDefault(_bars); + +/** + * @constructor + * @extends Dygraph.DataHandlers.BarsHandler + */ +var CustomBarsHandler = function CustomBarsHandler() {}; + +CustomBarsHandler.prototype = new _bars2['default'](); + +/** @inheritDoc */ +CustomBarsHandler.prototype.extractSeries = function (rawData, i, options) { + // TODO(danvk): pre-allocate series here. + var series = []; + var x, y, point; + var logScale = options.get('logscale'); + for (var j = 0; j < rawData.length; j++) { + x = rawData[j][0]; + point = rawData[j][i]; + if (logScale && point !== null) { + // On the log scale, points less than zero do not exist. + // This will create a gap in the chart. + if (point[0] <= 0 || point[1] <= 0 || point[2] <= 0) { + point = null; + } + } + // Extract to the unified data format. + if (point !== null) { + y = point[1]; + if (y !== null && !isNaN(y)) { + series.push([x, y, [point[0], point[2]]]); + } else { + series.push([x, y, [y, y]]); + } + } else { + series.push([x, null, [null, null]]); + } + } + return series; +}; + +/** @inheritDoc */ +CustomBarsHandler.prototype.rollingAverage = function (originalData, rollPeriod, options) { + rollPeriod = Math.min(rollPeriod, originalData.length); + var rollingData = []; + var y, low, high, mid, count, i, extremes; + + low = 0; + mid = 0; + high = 0; + count = 0; + for (i = 0; i < originalData.length; i++) { + y = originalData[i][1]; + extremes = originalData[i][2]; + rollingData[i] = originalData[i]; + + if (y !== null && !isNaN(y)) { + low += extremes[0]; + mid += y; + high += extremes[1]; + count += 1; + } + if (i - rollPeriod >= 0) { + var prev = originalData[i - rollPeriod]; + if (prev[1] !== null && !isNaN(prev[1])) { + low -= prev[2][0]; + mid -= prev[1]; + high -= prev[2][1]; + count -= 1; + } + } + if (count) { + rollingData[i] = [originalData[i][0], 1.0 * mid / count, [1.0 * low / count, 1.0 * high / count]]; + } else { + rollingData[i] = [originalData[i][0], null, [null, null]]; + } + } + + return rollingData; +}; + +exports['default'] = CustomBarsHandler; +module.exports = exports['default']; + +},{"./bars":5}],3:[function(require,module,exports){ +/** + * @license + * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview DataHandler implementation for the error bars option. + * @author David Eberlein (david.eberlein@ch.sauter-bc.com) + */ + +/*global Dygraph:false */ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +var _bars = require('./bars'); + +var _bars2 = _interopRequireDefault(_bars); + +/** + * @constructor + * @extends BarsHandler + */ +var ErrorBarsHandler = function ErrorBarsHandler() {}; + +ErrorBarsHandler.prototype = new _bars2["default"](); + +/** @inheritDoc */ +ErrorBarsHandler.prototype.extractSeries = function (rawData, i, options) { + // TODO(danvk): pre-allocate series here. + var series = []; + var x, y, variance, point; + var sigma = options.get("sigma"); + var logScale = options.get('logscale'); + for (var j = 0; j < rawData.length; j++) { + x = rawData[j][0]; + point = rawData[j][i]; + if (logScale && point !== null) { + // On the log scale, points less than zero do not exist. + // This will create a gap in the chart. + if (point[0] <= 0 || point[0] - sigma * point[1] <= 0) { + point = null; + } + } + // Extract to the unified data format. + if (point !== null) { + y = point[0]; + if (y !== null && !isNaN(y)) { + variance = sigma * point[1]; + // preserve original error value in extras for further + // filtering + series.push([x, y, [y - variance, y + variance, point[1]]]); + } else { + series.push([x, y, [y, y, y]]); + } + } else { + series.push([x, null, [null, null, null]]); + } + } + return series; +}; + +/** @inheritDoc */ +ErrorBarsHandler.prototype.rollingAverage = function (originalData, rollPeriod, options) { + rollPeriod = Math.min(rollPeriod, originalData.length); + var rollingData = []; + var sigma = options.get("sigma"); + + var i, j, y, v, sum, num_ok, stddev, variance, value; + + // Calculate the rolling average for the first rollPeriod - 1 points + // where there is not enough data to roll over the full number of points + for (i = 0; i < originalData.length; i++) { + sum = 0; + variance = 0; + num_ok = 0; + for (j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) { + y = originalData[j][1]; + if (y === null || isNaN(y)) continue; + num_ok++; + sum += y; + variance += Math.pow(originalData[j][2][2], 2); + } + if (num_ok) { + stddev = Math.sqrt(variance) / num_ok; + value = sum / num_ok; + rollingData[i] = [originalData[i][0], value, [value - sigma * stddev, value + sigma * stddev]]; + } else { + // This explicitly preserves NaNs to aid with "independent + // series". + // See testRollingAveragePreservesNaNs. + v = rollPeriod == 1 ? originalData[i][1] : null; + rollingData[i] = [originalData[i][0], v, [v, v]]; + } + } + + return rollingData; +}; + +exports["default"] = ErrorBarsHandler; +module.exports = exports["default"]; + +},{"./bars":5}],4:[function(require,module,exports){ +/** + * @license + * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview DataHandler implementation for the combination + * of error bars and fractions options. + * @author David Eberlein (david.eberlein@ch.sauter-bc.com) + */ + +/*global Dygraph:false */ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +var _bars = require('./bars'); + +var _bars2 = _interopRequireDefault(_bars); + +/** + * @constructor + * @extends Dygraph.DataHandlers.BarsHandler + */ +var FractionsBarsHandler = function FractionsBarsHandler() {}; + +FractionsBarsHandler.prototype = new _bars2["default"](); + +/** @inheritDoc */ +FractionsBarsHandler.prototype.extractSeries = function (rawData, i, options) { + // TODO(danvk): pre-allocate series here. + var series = []; + var x, y, point, num, den, value, stddev, variance; + var mult = 100.0; + var sigma = options.get("sigma"); + var logScale = options.get('logscale'); + for (var j = 0; j < rawData.length; j++) { + x = rawData[j][0]; + point = rawData[j][i]; + if (logScale && point !== null) { + // On the log scale, points less than zero do not exist. + // This will create a gap in the chart. + if (point[0] <= 0 || point[1] <= 0) { + point = null; + } + } + // Extract to the unified data format. + if (point !== null) { + num = point[0]; + den = point[1]; + if (num !== null && !isNaN(num)) { + value = den ? num / den : 0.0; + stddev = den ? sigma * Math.sqrt(value * (1 - value) / den) : 1.0; + variance = mult * stddev; + y = mult * value; + // preserve original values in extras for further filtering + series.push([x, y, [y - variance, y + variance, num, den]]); + } else { + series.push([x, num, [num, num, num, den]]); + } + } else { + series.push([x, null, [null, null, null, null]]); + } + } + return series; +}; + +/** @inheritDoc */ +FractionsBarsHandler.prototype.rollingAverage = function (originalData, rollPeriod, options) { + rollPeriod = Math.min(rollPeriod, originalData.length); + var rollingData = []; + var sigma = options.get("sigma"); + var wilsonInterval = options.get("wilsonInterval"); + + var low, high, i, stddev; + var num = 0; + var den = 0; // numerator/denominator + var mult = 100.0; + for (i = 0; i < originalData.length; i++) { + num += originalData[i][2][2]; + den += originalData[i][2][3]; + if (i - rollPeriod >= 0) { + num -= originalData[i - rollPeriod][2][2]; + den -= originalData[i - rollPeriod][2][3]; + } + + var date = originalData[i][0]; + var value = den ? num / den : 0.0; + if (wilsonInterval) { + // For more details on this confidence interval, see: + // http://en.wikipedia.org/wiki/Binomial_confidence_interval + if (den) { + var p = value < 0 ? 0 : value, + n = den; + var pm = sigma * Math.sqrt(p * (1 - p) / n + sigma * sigma / (4 * n * n)); + var denom = 1 + sigma * sigma / den; + low = (p + sigma * sigma / (2 * den) - pm) / denom; + high = (p + sigma * sigma / (2 * den) + pm) / denom; + rollingData[i] = [date, p * mult, [low * mult, high * mult]]; + } else { + rollingData[i] = [date, 0, [0, 0]]; + } + } else { + stddev = den ? sigma * Math.sqrt(value * (1 - value) / den) : 1.0; + rollingData[i] = [date, mult * value, [mult * (value - stddev), mult * (value + stddev)]]; + } + } + + return rollingData; +}; + +exports["default"] = FractionsBarsHandler; +module.exports = exports["default"]; + +},{"./bars":5}],5:[function(require,module,exports){ +/** + * @license + * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview DataHandler base implementation for the "bar" + * data formats. This implementation must be extended and the + * extractSeries and rollingAverage must be implemented. + * @author David Eberlein (david.eberlein@ch.sauter-bc.com) + */ + +/*global Dygraph:false */ +/*global DygraphLayout:false */ +"use strict"; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _datahandler = require('./datahandler'); + +var _datahandler2 = _interopRequireDefault(_datahandler); + +var _dygraphLayout = require('../dygraph-layout'); + +var _dygraphLayout2 = _interopRequireDefault(_dygraphLayout); + +/** + * @constructor + * @extends {Dygraph.DataHandler} + */ +var BarsHandler = function BarsHandler() { + _datahandler2['default'].call(this); +}; +BarsHandler.prototype = new _datahandler2['default'](); + +// TODO(danvk): figure out why the jsdoc has to be copy/pasted from superclass. +// (I get closure compiler errors if this isn't here.) +/** + * @override + * @param {!Array.} rawData The raw data passed into dygraphs where + * rawData[i] = [x,ySeries1,...,ySeriesN]. + * @param {!number} seriesIndex Index of the series to extract. All other + * series should be ignored. + * @param {!DygraphOptions} options Dygraph options. + * @return {Array.<[!number,?number,?]>} The series in the unified data format + * where series[i] = [x,y,{extras}]. + */ +BarsHandler.prototype.extractSeries = function (rawData, seriesIndex, options) { + // Not implemented here must be extended +}; + +/** + * @override + * @param {!Array.<[!number,?number,?]>} series The series in the unified + * data format where series[i] = [x,y,{extras}]. + * @param {!number} rollPeriod The number of points over which to average the data + * @param {!DygraphOptions} options The dygraph options. + * TODO(danvk): be more specific than "Array" here. + * @return {!Array.<[!number,?number,?]>} the rolled series. + */ +BarsHandler.prototype.rollingAverage = function (series, rollPeriod, options) { + // Not implemented here, must be extended. +}; + +/** @inheritDoc */ +BarsHandler.prototype.onPointsCreated_ = function (series, points) { + for (var i = 0; i < series.length; ++i) { + var item = series[i]; + var point = points[i]; + point.y_top = NaN; + point.y_bottom = NaN; + point.yval_minus = _datahandler2['default'].parseFloat(item[2][0]); + point.yval_plus = _datahandler2['default'].parseFloat(item[2][1]); + } +}; + +/** @inheritDoc */ +BarsHandler.prototype.getExtremeYValues = function (series, dateWindow, options) { + var minY = null, + maxY = null, + y; + + var firstIdx = 0; + var lastIdx = series.length - 1; + + for (var j = firstIdx; j <= lastIdx; j++) { + y = series[j][1]; + if (y === null || isNaN(y)) continue; + + var low = series[j][2][0]; + var high = series[j][2][1]; + + if (low > y) low = y; // this can happen with custom bars, + if (high < y) high = y; // e.g. in tests/custom-bars.html + + if (maxY === null || high > maxY) maxY = high; + if (minY === null || low < minY) minY = low; + } + + return [minY, maxY]; +}; + +/** @inheritDoc */ +BarsHandler.prototype.onLineEvaluated = function (points, axis, logscale) { + var point; + for (var j = 0; j < points.length; j++) { + // Copy over the error terms + point = points[j]; + point.y_top = _dygraphLayout2['default'].calcYNormal_(axis, point.yval_minus, logscale); + point.y_bottom = _dygraphLayout2['default'].calcYNormal_(axis, point.yval_plus, logscale); + } +}; + +exports['default'] = BarsHandler; +module.exports = exports['default']; + +},{"../dygraph-layout":13,"./datahandler":6}],6:[function(require,module,exports){ +/** + * @license + * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview This file contains the managment of data handlers + * @author David Eberlein (david.eberlein@ch.sauter-bc.com) + * + * The idea is to define a common, generic data format that works for all data + * structures supported by dygraphs. To make this possible, the DataHandler + * interface is introduced. This makes it possible, that dygraph itself can work + * with the same logic for every data type independent of the actual format and + * the DataHandler takes care of the data format specific jobs. + * DataHandlers are implemented for all data types supported by Dygraphs and + * return Dygraphs compliant formats. + * By default the correct DataHandler is chosen based on the options set. + * Optionally the user may use his own DataHandler (similar to the plugin + * system). + * + * + * The unified data format returend by each handler is defined as so: + * series[n][point] = [x,y,(extras)] + * + * This format contains the common basis that is needed to draw a simple line + * series extended by optional extras for more complex graphing types. It + * contains a primitive x value as first array entry, a primitive y value as + * second array entry and an optional extras object for additional data needed. + * + * x must always be a number. + * y must always be a number, NaN of type number or null. + * extras is optional and must be interpreted by the DataHandler. It may be of + * any type. + * + * In practice this might look something like this: + * default: [x, yVal] + * errorBar / customBar: [x, yVal, [yTopVariance, yBottomVariance] ] + * + */ +/*global Dygraph:false */ +/*global DygraphLayout:false */ + +"use strict"; + +/** + * + * The data handler is responsible for all data specific operations. All of the + * series data it receives and returns is always in the unified data format. + * Initially the unified data is created by the extractSeries method + * @constructor + */ +Object.defineProperty(exports, "__esModule", { + value: true +}); +var DygraphDataHandler = function DygraphDataHandler() {}; + +var handler = DygraphDataHandler; + +/** + * X-value array index constant for unified data samples. + * @const + * @type {number} + */ +handler.X = 0; + +/** + * Y-value array index constant for unified data samples. + * @const + * @type {number} + */ +handler.Y = 1; + +/** + * Extras-value array index constant for unified data samples. + * @const + * @type {number} + */ +handler.EXTRAS = 2; + +/** + * Extracts one series from the raw data (a 2D array) into an array of the + * unified data format. + * This is where undesirable points (i.e. negative values on log scales and + * missing values through which we wish to connect lines) are dropped. + * TODO(danvk): the "missing values" bit above doesn't seem right. + * + * @param {!Array.} rawData The raw data passed into dygraphs where + * rawData[i] = [x,ySeries1,...,ySeriesN]. + * @param {!number} seriesIndex Index of the series to extract. All other + * series should be ignored. + * @param {!DygraphOptions} options Dygraph options. + * @return {Array.<[!number,?number,?]>} The series in the unified data format + * where series[i] = [x,y,{extras}]. + */ +handler.prototype.extractSeries = function (rawData, seriesIndex, options) {}; + +/** + * Converts a series to a Point array. The resulting point array must be + * returned in increasing order of idx property. + * + * @param {!Array.<[!number,?number,?]>} series The series in the unified + * data format where series[i] = [x,y,{extras}]. + * @param {!string} setName Name of the series. + * @param {!number} boundaryIdStart Index offset of the first point, equal to the + * number of skipped points left of the date window minimum (if any). + * @return {!Array.} List of points for this series. + */ +handler.prototype.seriesToPoints = function (series, setName, boundaryIdStart) { + // TODO(bhs): these loops are a hot-spot for high-point-count charts. In + // fact, + // on chrome+linux, they are 6 times more expensive than iterating through + // the + // points and drawing the lines. The brunt of the cost comes from allocating + // the |point| structures. + var points = []; + for (var i = 0; i < series.length; ++i) { + var item = series[i]; + var yraw = item[1]; + var yval = yraw === null ? null : handler.parseFloat(yraw); + var point = { + x: NaN, + y: NaN, + xval: handler.parseFloat(item[0]), + yval: yval, + name: setName, // TODO(danvk): is this really necessary? + idx: i + boundaryIdStart + }; + points.push(point); + } + this.onPointsCreated_(series, points); + return points; +}; + +/** + * Callback called for each series after the series points have been generated + * which will later be used by the plotters to draw the graph. + * Here data may be added to the seriesPoints which is needed by the plotters. + * The indexes of series and points are in sync meaning the original data + * sample for series[i] is points[i]. + * + * @param {!Array.<[!number,?number,?]>} series The series in the unified + * data format where series[i] = [x,y,{extras}]. + * @param {!Array.} points The corresponding points passed + * to the plotter. + * @protected + */ +handler.prototype.onPointsCreated_ = function (series, points) {}; + +/** + * Calculates the rolling average of a data set. + * + * @param {!Array.<[!number,?number,?]>} series The series in the unified + * data format where series[i] = [x,y,{extras}]. + * @param {!number} rollPeriod The number of points over which to average the data + * @param {!DygraphOptions} options The dygraph options. + * @return {!Array.<[!number,?number,?]>} the rolled series. + */ +handler.prototype.rollingAverage = function (series, rollPeriod, options) {}; + +/** + * Computes the range of the data series (including confidence intervals). + * + * @param {!Array.<[!number,?number,?]>} series The series in the unified + * data format where series[i] = [x, y, {extras}]. + * @param {!Array.} dateWindow The x-value range to display with + * the format: [min, max]. + * @param {!DygraphOptions} options The dygraph options. + * @return {Array.} The low and high extremes of the series in the + * given window with the format: [low, high]. + */ +handler.prototype.getExtremeYValues = function (series, dateWindow, options) {}; + +/** + * Callback called for each series after the layouting data has been + * calculated before the series is drawn. Here normalized positioning data + * should be calculated for the extras of each point. + * + * @param {!Array.} points The points passed to + * the plotter. + * @param {!Object} axis The axis on which the series will be plotted. + * @param {!boolean} logscale Weather or not to use a logscale. + */ +handler.prototype.onLineEvaluated = function (points, axis, logscale) {}; + +/** + * Optimized replacement for parseFloat, which was way too slow when almost + * all values were type number, with few edge cases, none of which were strings. + * @param {?number} val + * @return {number} + * @protected + */ +handler.parseFloat = function (val) { + // parseFloat(null) is NaN + if (val === null) { + return NaN; + } + + // Assume it's a number or NaN. If it's something else, I'll be shocked. + return val; +}; + +exports["default"] = DygraphDataHandler; +module.exports = exports["default"]; + +},{}],7:[function(require,module,exports){ +/** + * @license + * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview DataHandler implementation for the fractions option. + * @author David Eberlein (david.eberlein@ch.sauter-bc.com) + */ + +/*global Dygraph:false */ +"use strict"; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _datahandler = require('./datahandler'); + +var _datahandler2 = _interopRequireDefault(_datahandler); + +var _default = require('./default'); + +var _default2 = _interopRequireDefault(_default); + +/** + * @extends DefaultHandler + * @constructor + */ +var DefaultFractionHandler = function DefaultFractionHandler() {}; + +DefaultFractionHandler.prototype = new _default2['default'](); + +DefaultFractionHandler.prototype.extractSeries = function (rawData, i, options) { + // TODO(danvk): pre-allocate series here. + var series = []; + var x, y, point, num, den, value; + var mult = 100.0; + var logScale = options.get('logscale'); + for (var j = 0; j < rawData.length; j++) { + x = rawData[j][0]; + point = rawData[j][i]; + if (logScale && point !== null) { + // On the log scale, points less than zero do not exist. + // This will create a gap in the chart. + if (point[0] <= 0 || point[1] <= 0) { + point = null; + } + } + // Extract to the unified data format. + if (point !== null) { + num = point[0]; + den = point[1]; + if (num !== null && !isNaN(num)) { + value = den ? num / den : 0.0; + y = mult * value; + // preserve original values in extras for further filtering + series.push([x, y, [num, den]]); + } else { + series.push([x, num, [num, den]]); + } + } else { + series.push([x, null, [null, null]]); + } + } + return series; +}; + +DefaultFractionHandler.prototype.rollingAverage = function (originalData, rollPeriod, options) { + rollPeriod = Math.min(rollPeriod, originalData.length); + var rollingData = []; + + var i; + var num = 0; + var den = 0; // numerator/denominator + var mult = 100.0; + for (i = 0; i < originalData.length; i++) { + num += originalData[i][2][0]; + den += originalData[i][2][1]; + if (i - rollPeriod >= 0) { + num -= originalData[i - rollPeriod][2][0]; + den -= originalData[i - rollPeriod][2][1]; + } + + var date = originalData[i][0]; + var value = den ? num / den : 0.0; + rollingData[i] = [date, mult * value]; + } + + return rollingData; +}; + +exports['default'] = DefaultFractionHandler; +module.exports = exports['default']; + +},{"./datahandler":6,"./default":8}],8:[function(require,module,exports){ +/** + * @license + * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview DataHandler default implementation used for simple line charts. + * @author David Eberlein (david.eberlein@ch.sauter-bc.com) + */ + +/*global Dygraph:false */ +"use strict"; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _datahandler = require('./datahandler'); + +var _datahandler2 = _interopRequireDefault(_datahandler); + +/** + * @constructor + * @extends Dygraph.DataHandler + */ +var DefaultHandler = function DefaultHandler() {}; + +DefaultHandler.prototype = new _datahandler2['default'](); + +/** @inheritDoc */ +DefaultHandler.prototype.extractSeries = function (rawData, i, options) { + // TODO(danvk): pre-allocate series here. + var series = []; + var logScale = options.get('logscale'); + for (var j = 0; j < rawData.length; j++) { + var x = rawData[j][0]; + var point = rawData[j][i]; + if (logScale) { + // On the log scale, points less than zero do not exist. + // This will create a gap in the chart. + if (point <= 0) { + point = null; + } + } + series.push([x, point]); + } + return series; +}; + +/** @inheritDoc */ +DefaultHandler.prototype.rollingAverage = function (originalData, rollPeriod, options) { + rollPeriod = Math.min(rollPeriod, originalData.length); + var rollingData = []; + + var i, j, y, sum, num_ok; + // Calculate the rolling average for the first rollPeriod - 1 points + // where + // there is not enough data to roll over the full number of points + if (rollPeriod == 1) { + return originalData; + } + for (i = 0; i < originalData.length; i++) { + sum = 0; + num_ok = 0; + for (j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) { + y = originalData[j][1]; + if (y === null || isNaN(y)) continue; + num_ok++; + sum += originalData[j][1]; + } + if (num_ok) { + rollingData[i] = [originalData[i][0], sum / num_ok]; + } else { + rollingData[i] = [originalData[i][0], null]; + } + } + + return rollingData; +}; + +/** @inheritDoc */ +DefaultHandler.prototype.getExtremeYValues = function (series, dateWindow, options) { + var minY = null, + maxY = null, + y; + var firstIdx = 0, + lastIdx = series.length - 1; + + for (var j = firstIdx; j <= lastIdx; j++) { + y = series[j][1]; + if (y === null || isNaN(y)) continue; + if (maxY === null || y > maxY) { + maxY = y; + } + if (minY === null || y < minY) { + minY = y; + } + } + return [minY, maxY]; +}; + +exports['default'] = DefaultHandler; +module.exports = exports['default']; + +},{"./datahandler":6}],9:[function(require,module,exports){ +/** + * @license + * Copyright 2006 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview Based on PlotKit.CanvasRenderer, but modified to meet the + * needs of dygraphs. + * + * In particular, support for: + * - grid overlays + * - error bars + * - dygraphs attribute system + */ + +/** + * The DygraphCanvasRenderer class does the actual rendering of the chart onto + * a canvas. It's based on PlotKit.CanvasRenderer. + * @param {Object} element The canvas to attach to + * @param {Object} elementContext The 2d context of the canvas (injected so it + * can be mocked for testing.) + * @param {Layout} layout The DygraphLayout object for this graph. + * @constructor + */ + +/*global Dygraph:false */ +"use strict"; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +var _dygraphUtils = require('./dygraph-utils'); + +var utils = _interopRequireWildcard(_dygraphUtils); + +var _dygraph = require('./dygraph'); + +var _dygraph2 = _interopRequireDefault(_dygraph); + +/** + * @constructor + * + * This gets called when there are "new points" to chart. This is generally the + * case when the underlying data being charted has changed. It is _not_ called + * in the common case that the user has zoomed or is panning the view. + * + * The chart canvas has already been created by the Dygraph object. The + * renderer simply gets a drawing context. + * + * @param {Dygraph} dygraph The chart to which this renderer belongs. + * @param {HTMLCanvasElement} element The <canvas> DOM element on which to draw. + * @param {CanvasRenderingContext2D} elementContext The drawing context. + * @param {DygraphLayout} layout The chart's DygraphLayout object. + * + * TODO(danvk): remove the elementContext property. + */ +var DygraphCanvasRenderer = function DygraphCanvasRenderer(dygraph, element, elementContext, layout) { + this.dygraph_ = dygraph; + + this.layout = layout; + this.element = element; + this.elementContext = elementContext; + + this.height = dygraph.height_; + this.width = dygraph.width_; + + // --- check whether everything is ok before we return + if (!utils.isCanvasSupported(this.element)) { + throw "Canvas is not supported."; + } + + // internal state + this.area = layout.getPlotArea(); + + // Set up a clipping area for the canvas (and the interaction canvas). + // This ensures that we don't overdraw. + var ctx = this.dygraph_.canvas_ctx_; + ctx.beginPath(); + ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h); + ctx.clip(); + + ctx = this.dygraph_.hidden_ctx_; + ctx.beginPath(); + ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h); + ctx.clip(); +}; + +/** + * Clears out all chart content and DOM elements. + * This is called immediately before render() on every frame, including + * during zooms and pans. + * @private + */ +DygraphCanvasRenderer.prototype.clear = function () { + this.elementContext.clearRect(0, 0, this.width, this.height); +}; + +/** + * This method is responsible for drawing everything on the chart, including + * lines, error bars, fills and axes. + * It is called immediately after clear() on every frame, including during pans + * and zooms. + * @private + */ +DygraphCanvasRenderer.prototype.render = function () { + // attaches point.canvas{x,y} + this._updatePoints(); + + // actually draws the chart. + this._renderLineChart(); +}; + +/** + * Returns a predicate to be used with an iterator, which will + * iterate over points appropriately, depending on whether + * connectSeparatedPoints is true. When it's false, the predicate will + * skip over points with missing yVals. + */ +DygraphCanvasRenderer._getIteratorPredicate = function (connectSeparatedPoints) { + return connectSeparatedPoints ? DygraphCanvasRenderer._predicateThatSkipsEmptyPoints : null; +}; + +DygraphCanvasRenderer._predicateThatSkipsEmptyPoints = function (array, idx) { + return array[idx].yval !== null; +}; + +/** + * Draws a line with the styles passed in and calls all the drawPointCallbacks. + * @param {Object} e The dictionary passed to the plotter function. + * @private + */ +DygraphCanvasRenderer._drawStyledLine = function (e, color, strokeWidth, strokePattern, drawPoints, drawPointCallback, pointSize) { + var g = e.dygraph; + // TODO(konigsberg): Compute attributes outside this method call. + var stepPlot = g.getBooleanOption("stepPlot", e.setName); + + if (!utils.isArrayLike(strokePattern)) { + strokePattern = null; + } + + var drawGapPoints = g.getBooleanOption('drawGapEdgePoints', e.setName); + + var points = e.points; + var setName = e.setName; + var iter = utils.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate(g.getBooleanOption("connectSeparatedPoints", setName))); + + var stroking = strokePattern && strokePattern.length >= 2; + + var ctx = e.drawingContext; + ctx.save(); + if (stroking) { + if (ctx.setLineDash) ctx.setLineDash(strokePattern); + } + + var pointsOnLine = DygraphCanvasRenderer._drawSeries(e, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color); + DygraphCanvasRenderer._drawPointsOnLine(e, pointsOnLine, drawPointCallback, color, pointSize); + + if (stroking) { + if (ctx.setLineDash) ctx.setLineDash([]); + } + + ctx.restore(); +}; + +/** + * This does the actual drawing of lines on the canvas, for just one series. + * Returns a list of [canvasx, canvasy] pairs for points for which a + * drawPointCallback should be fired. These include isolated points, or all + * points if drawPoints=true. + * @param {Object} e The dictionary passed to the plotter function. + * @private + */ +DygraphCanvasRenderer._drawSeries = function (e, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color) { + + var prevCanvasX = null; + var prevCanvasY = null; + var nextCanvasY = null; + var isIsolated; // true if this point is isolated (no line segments) + var point; // the point being processed in the while loop + var pointsOnLine = []; // Array of [canvasx, canvasy] pairs. + var first = true; // the first cycle through the while loop + + var ctx = e.drawingContext; + ctx.beginPath(); + ctx.strokeStyle = color; + ctx.lineWidth = strokeWidth; + + // NOTE: we break the iterator's encapsulation here for about a 25% speedup. + var arr = iter.array_; + var limit = iter.end_; + var predicate = iter.predicate_; + + for (var i = iter.start_; i < limit; i++) { + point = arr[i]; + if (predicate) { + while (i < limit && !predicate(arr, i)) { + i++; + } + if (i == limit) break; + point = arr[i]; + } + + // FIXME: The 'canvasy != canvasy' test here catches NaN values but the test + // doesn't catch Infinity values. Could change this to + // !isFinite(point.canvasy), but I assume it avoids isNaN for performance? + if (point.canvasy === null || point.canvasy != point.canvasy) { + if (stepPlot && prevCanvasX !== null) { + // Draw a horizontal line to the start of the missing data + ctx.moveTo(prevCanvasX, prevCanvasY); + ctx.lineTo(point.canvasx, prevCanvasY); + } + prevCanvasX = prevCanvasY = null; + } else { + isIsolated = false; + if (drawGapPoints || prevCanvasX === null) { + iter.nextIdx_ = i; + iter.next(); + nextCanvasY = iter.hasNext ? iter.peek.canvasy : null; + + var isNextCanvasYNullOrNaN = nextCanvasY === null || nextCanvasY != nextCanvasY; + isIsolated = prevCanvasX === null && isNextCanvasYNullOrNaN; + if (drawGapPoints) { + // Also consider a point to be "isolated" if it's adjacent to a + // null point, excluding the graph edges. + if (!first && prevCanvasX === null || iter.hasNext && isNextCanvasYNullOrNaN) { + isIsolated = true; + } + } + } + + if (prevCanvasX !== null) { + if (strokeWidth) { + if (stepPlot) { + ctx.moveTo(prevCanvasX, prevCanvasY); + ctx.lineTo(point.canvasx, prevCanvasY); + } + + ctx.lineTo(point.canvasx, point.canvasy); + } + } else { + ctx.moveTo(point.canvasx, point.canvasy); + } + if (drawPoints || isIsolated) { + pointsOnLine.push([point.canvasx, point.canvasy, point.idx]); + } + prevCanvasX = point.canvasx; + prevCanvasY = point.canvasy; + } + first = false; + } + ctx.stroke(); + return pointsOnLine; +}; + +/** + * This fires the drawPointCallback functions, which draw dots on the points by + * default. This gets used when the "drawPoints" option is set, or when there + * are isolated points. + * @param {Object} e The dictionary passed to the plotter function. + * @private + */ +DygraphCanvasRenderer._drawPointsOnLine = function (e, pointsOnLine, drawPointCallback, color, pointSize) { + var ctx = e.drawingContext; + for (var idx = 0; idx < pointsOnLine.length; idx++) { + var cb = pointsOnLine[idx]; + ctx.save(); + drawPointCallback.call(e.dygraph, e.dygraph, e.setName, ctx, cb[0], cb[1], color, pointSize, cb[2]); + ctx.restore(); + } +}; + +/** + * Attaches canvas coordinates to the points array. + * @private + */ +DygraphCanvasRenderer.prototype._updatePoints = function () { + // Update Points + // TODO(danvk): here + // + // TODO(bhs): this loop is a hot-spot for high-point-count charts. These + // transformations can be pushed into the canvas via linear transformation + // matrices. + // NOTE(danvk): this is trickier than it sounds at first. The transformation + // needs to be done before the .moveTo() and .lineTo() calls, but must be + // undone before the .stroke() call to ensure that the stroke width is + // unaffected. An alternative is to reduce the stroke width in the + // transformed coordinate space, but you can't specify different values for + // each dimension (as you can with .scale()). The speedup here is ~12%. + var sets = this.layout.points; + for (var i = sets.length; i--;) { + var points = sets[i]; + for (var j = points.length; j--;) { + var point = points[j]; + point.canvasx = this.area.w * point.x + this.area.x; + point.canvasy = this.area.h * point.y + this.area.y; + } + } +}; + +/** + * Add canvas Actually draw the lines chart, including error bars. + * + * This function can only be called if DygraphLayout's points array has been + * updated with canvas{x,y} attributes, i.e. by + * DygraphCanvasRenderer._updatePoints. + * + * @param {string=} opt_seriesName when specified, only that series will + * be drawn. (This is used for expedited redrawing with highlightSeriesOpts) + * @param {CanvasRenderingContext2D} opt_ctx when specified, the drawing + * context. However, lines are typically drawn on the object's + * elementContext. + * @private + */ +DygraphCanvasRenderer.prototype._renderLineChart = function (opt_seriesName, opt_ctx) { + var ctx = opt_ctx || this.elementContext; + var i; + + var sets = this.layout.points; + var setNames = this.layout.setNames; + var setName; + + this.colors = this.dygraph_.colorsMap_; + + // Determine which series have specialized plotters. + var plotter_attr = this.dygraph_.getOption("plotter"); + var plotters = plotter_attr; + if (!utils.isArrayLike(plotters)) { + plotters = [plotters]; + } + + var setPlotters = {}; // series name -> plotter fn. + for (i = 0; i < setNames.length; i++) { + setName = setNames[i]; + var setPlotter = this.dygraph_.getOption("plotter", setName); + if (setPlotter == plotter_attr) continue; // not specialized. + + setPlotters[setName] = setPlotter; + } + + for (i = 0; i < plotters.length; i++) { + var plotter = plotters[i]; + var is_last = i == plotters.length - 1; + + for (var j = 0; j < sets.length; j++) { + setName = setNames[j]; + if (opt_seriesName && setName != opt_seriesName) continue; + + var points = sets[j]; + + // Only throw in the specialized plotters on the last iteration. + var p = plotter; + if (setName in setPlotters) { + if (is_last) { + p = setPlotters[setName]; + } else { + // Don't use the standard plotters in this case. + continue; + } + } + + var color = this.colors[setName]; + var strokeWidth = this.dygraph_.getOption("strokeWidth", setName); + + ctx.save(); + ctx.strokeStyle = color; + ctx.lineWidth = strokeWidth; + p({ + points: points, + setName: setName, + drawingContext: ctx, + color: color, + strokeWidth: strokeWidth, + dygraph: this.dygraph_, + axis: this.dygraph_.axisPropertiesForSeries(setName), + plotArea: this.area, + seriesIndex: j, + seriesCount: sets.length, + singleSeriesName: opt_seriesName, + allSeriesPoints: sets + }); + ctx.restore(); + } + } +}; + +/** + * Standard plotters. These may be used by clients via Dygraph.Plotters. + * See comments there for more details. + */ +DygraphCanvasRenderer._Plotters = { + linePlotter: function linePlotter(e) { + DygraphCanvasRenderer._linePlotter(e); + }, + + fillPlotter: function fillPlotter(e) { + DygraphCanvasRenderer._fillPlotter(e); + }, + + errorPlotter: function errorPlotter(e) { + DygraphCanvasRenderer._errorPlotter(e); + } +}; + +/** + * Plotter which draws the central lines for a series. + * @private + */ +DygraphCanvasRenderer._linePlotter = function (e) { + var g = e.dygraph; + var setName = e.setName; + var strokeWidth = e.strokeWidth; + + // TODO(danvk): Check if there's any performance impact of just calling + // getOption() inside of _drawStyledLine. Passing in so many parameters makes + // this code a bit nasty. + var borderWidth = g.getNumericOption("strokeBorderWidth", setName); + var drawPointCallback = g.getOption("drawPointCallback", setName) || utils.Circles.DEFAULT; + var strokePattern = g.getOption("strokePattern", setName); + var drawPoints = g.getBooleanOption("drawPoints", setName); + var pointSize = g.getNumericOption("pointSize", setName); + + if (borderWidth && strokeWidth) { + DygraphCanvasRenderer._drawStyledLine(e, g.getOption("strokeBorderColor", setName), strokeWidth + 2 * borderWidth, strokePattern, drawPoints, drawPointCallback, pointSize); + } + + DygraphCanvasRenderer._drawStyledLine(e, e.color, strokeWidth, strokePattern, drawPoints, drawPointCallback, pointSize); +}; + +/** + * Draws the shaded error bars/confidence intervals for each series. + * This happens before the center lines are drawn, since the center lines + * need to be drawn on top of the error bars for all series. + * @private + */ +DygraphCanvasRenderer._errorPlotter = function (e) { + var g = e.dygraph; + var setName = e.setName; + var errorBars = g.getBooleanOption("errorBars") || g.getBooleanOption("customBars"); + if (!errorBars) return; + + var fillGraph = g.getBooleanOption("fillGraph", setName); + if (fillGraph) { + console.warn("Can't use fillGraph option with error bars"); + } + + var ctx = e.drawingContext; + var color = e.color; + var fillAlpha = g.getNumericOption('fillAlpha', setName); + var stepPlot = g.getBooleanOption("stepPlot", setName); + var points = e.points; + + var iter = utils.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate(g.getBooleanOption("connectSeparatedPoints", setName))); + + var newYs; + + // setup graphics context + var prevX = NaN; + var prevY = NaN; + var prevYs = [-1, -1]; + // should be same color as the lines but only 15% opaque. + var rgb = utils.toRGB_(color); + var err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')'; + ctx.fillStyle = err_color; + ctx.beginPath(); + + var isNullUndefinedOrNaN = function isNullUndefinedOrNaN(x) { + return x === null || x === undefined || isNaN(x); + }; + + while (iter.hasNext) { + var point = iter.next(); + if (!stepPlot && isNullUndefinedOrNaN(point.y) || stepPlot && !isNaN(prevY) && isNullUndefinedOrNaN(prevY)) { + prevX = NaN; + continue; + } + + newYs = [point.y_bottom, point.y_top]; + if (stepPlot) { + prevY = point.y; + } + + // The documentation specifically disallows nulls inside the point arrays, + // but in case it happens we should do something sensible. + if (isNaN(newYs[0])) newYs[0] = point.y; + if (isNaN(newYs[1])) newYs[1] = point.y; + + newYs[0] = e.plotArea.h * newYs[0] + e.plotArea.y; + newYs[1] = e.plotArea.h * newYs[1] + e.plotArea.y; + if (!isNaN(prevX)) { + if (stepPlot) { + ctx.moveTo(prevX, prevYs[0]); + ctx.lineTo(point.canvasx, prevYs[0]); + ctx.lineTo(point.canvasx, prevYs[1]); + } else { + ctx.moveTo(prevX, prevYs[0]); + ctx.lineTo(point.canvasx, newYs[0]); + ctx.lineTo(point.canvasx, newYs[1]); + } + ctx.lineTo(prevX, prevYs[1]); + ctx.closePath(); + } + prevYs = newYs; + prevX = point.canvasx; + } + ctx.fill(); +}; + +/** + * Proxy for CanvasRenderingContext2D which drops moveTo/lineTo calls which are + * superfluous. It accumulates all movements which haven't changed the x-value + * and only applies the two with the most extreme y-values. + * + * Calls to lineTo/moveTo must have non-decreasing x-values. + */ +DygraphCanvasRenderer._fastCanvasProxy = function (context) { + var pendingActions = []; // array of [type, x, y] tuples + var lastRoundedX = null; + var lastFlushedX = null; + + var LINE_TO = 1, + MOVE_TO = 2; + + var actionCount = 0; // number of moveTos and lineTos passed to context. + + // Drop superfluous motions + // Assumes all pendingActions have the same (rounded) x-value. + var compressActions = function compressActions(opt_losslessOnly) { + if (pendingActions.length <= 1) return; + + // Lossless compression: drop inconsequential moveTos. + for (var i = pendingActions.length - 1; i > 0; i--) { + var action = pendingActions[i]; + if (action[0] == MOVE_TO) { + var prevAction = pendingActions[i - 1]; + if (prevAction[1] == action[1] && prevAction[2] == action[2]) { + pendingActions.splice(i, 1); + } + } + } + + // Lossless compression: ... drop consecutive moveTos ... + for (var i = 0; i < pendingActions.length - 1;) /* incremented internally */{ + var action = pendingActions[i]; + if (action[0] == MOVE_TO && pendingActions[i + 1][0] == MOVE_TO) { + pendingActions.splice(i, 1); + } else { + i++; + } + } + + // Lossy compression: ... drop all but the extreme y-values ... + if (pendingActions.length > 2 && !opt_losslessOnly) { + // keep an initial moveTo, but drop all others. + var startIdx = 0; + if (pendingActions[0][0] == MOVE_TO) startIdx++; + var minIdx = null, + maxIdx = null; + for (var i = startIdx; i < pendingActions.length; i++) { + var action = pendingActions[i]; + if (action[0] != LINE_TO) continue; + if (minIdx === null && maxIdx === null) { + minIdx = i; + maxIdx = i; + } else { + var y = action[2]; + if (y < pendingActions[minIdx][2]) { + minIdx = i; + } else if (y > pendingActions[maxIdx][2]) { + maxIdx = i; + } + } + } + var minAction = pendingActions[minIdx], + maxAction = pendingActions[maxIdx]; + pendingActions.splice(startIdx, pendingActions.length - startIdx); + if (minIdx < maxIdx) { + pendingActions.push(minAction); + pendingActions.push(maxAction); + } else if (minIdx > maxIdx) { + pendingActions.push(maxAction); + pendingActions.push(minAction); + } else { + pendingActions.push(minAction); + } + } + }; + + var flushActions = function flushActions(opt_noLossyCompression) { + compressActions(opt_noLossyCompression); + for (var i = 0, len = pendingActions.length; i < len; i++) { + var action = pendingActions[i]; + if (action[0] == LINE_TO) { + context.lineTo(action[1], action[2]); + } else if (action[0] == MOVE_TO) { + context.moveTo(action[1], action[2]); + } + } + if (pendingActions.length) { + lastFlushedX = pendingActions[pendingActions.length - 1][1]; + } + actionCount += pendingActions.length; + pendingActions = []; + }; + + var addAction = function addAction(action, x, y) { + var rx = Math.round(x); + if (lastRoundedX === null || rx != lastRoundedX) { + // if there are large gaps on the x-axis, it's essential to keep the + // first and last point as well. + var hasGapOnLeft = lastRoundedX - lastFlushedX > 1, + hasGapOnRight = rx - lastRoundedX > 1, + hasGap = hasGapOnLeft || hasGapOnRight; + flushActions(hasGap); + lastRoundedX = rx; + } + pendingActions.push([action, x, y]); + }; + + return { + moveTo: function moveTo(x, y) { + addAction(MOVE_TO, x, y); + }, + lineTo: function lineTo(x, y) { + addAction(LINE_TO, x, y); + }, + + // for major operations like stroke/fill, we skip compression to ensure + // that there are no artifacts at the right edge. + stroke: function stroke() { + flushActions(true);context.stroke(); + }, + fill: function fill() { + flushActions(true);context.fill(); + }, + beginPath: function beginPath() { + flushActions(true);context.beginPath(); + }, + closePath: function closePath() { + flushActions(true);context.closePath(); + }, + + _count: function _count() { + return actionCount; + } + }; +}; + +/** + * Draws the shaded regions when "fillGraph" is set. Not to be confused with + * error bars. + * + * For stacked charts, it's more convenient to handle all the series + * simultaneously. So this plotter plots all the points on the first series + * it's asked to draw, then ignores all the other series. + * + * @private + */ +DygraphCanvasRenderer._fillPlotter = function (e) { + // Skip if we're drawing a single series for interactive highlight overlay. + if (e.singleSeriesName) return; + + // We'll handle all the series at once, not one-by-one. + if (e.seriesIndex !== 0) return; + + var g = e.dygraph; + var setNames = g.getLabels().slice(1); // remove x-axis + + // getLabels() includes names for invisible series, which are not included in + // allSeriesPoints. We remove those to make the two match. + // TODO(danvk): provide a simpler way to get this information. + for (var i = setNames.length; i >= 0; i--) { + if (!g.visibility()[i]) setNames.splice(i, 1); + } + + var anySeriesFilled = (function () { + for (var i = 0; i < setNames.length; i++) { + if (g.getBooleanOption("fillGraph", setNames[i])) return true; + } + return false; + })(); + + if (!anySeriesFilled) return; + + var area = e.plotArea; + var sets = e.allSeriesPoints; + var setCount = sets.length; + + var stackedGraph = g.getBooleanOption("stackedGraph"); + var colors = g.getColors(); + + // For stacked graphs, track the baseline for filling. + // + // The filled areas below graph lines are trapezoids with two + // vertical edges. The top edge is the line segment being drawn, and + // the baseline is the bottom edge. Each baseline corresponds to the + // top line segment from the previous stacked line. In the case of + // step plots, the trapezoids are rectangles. + var baseline = {}; + var currBaseline; + var prevStepPlot; // for different line drawing modes (line/step) per series + + // Helper function to trace a line back along the baseline. + var traceBackPath = function traceBackPath(ctx, baselineX, baselineY, pathBack) { + ctx.lineTo(baselineX, baselineY); + if (stackedGraph) { + for (var i = pathBack.length - 1; i >= 0; i--) { + var pt = pathBack[i]; + ctx.lineTo(pt[0], pt[1]); + } + } + }; + + // process sets in reverse order (needed for stacked graphs) + for (var setIdx = setCount - 1; setIdx >= 0; setIdx--) { + var ctx = e.drawingContext; + var setName = setNames[setIdx]; + if (!g.getBooleanOption('fillGraph', setName)) continue; + + var fillAlpha = g.getNumericOption('fillAlpha', setName); + var stepPlot = g.getBooleanOption('stepPlot', setName); + var color = colors[setIdx]; + var axis = g.axisPropertiesForSeries(setName); + var axisY = 1.0 + axis.minyval * axis.yscale; + if (axisY < 0.0) axisY = 0.0;else if (axisY > 1.0) axisY = 1.0; + axisY = area.h * axisY + area.y; + + var points = sets[setIdx]; + var iter = utils.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate(g.getBooleanOption("connectSeparatedPoints", setName))); + + // setup graphics context + var prevX = NaN; + var prevYs = [-1, -1]; + var newYs; + // should be same color as the lines but only 15% opaque. + var rgb = utils.toRGB_(color); + var err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')'; + ctx.fillStyle = err_color; + ctx.beginPath(); + var last_x, + is_first = true; + + // If the point density is high enough, dropping segments on their way to + // the canvas justifies the overhead of doing so. + if (points.length > 2 * g.width_ || _dygraph2['default'].FORCE_FAST_PROXY) { + ctx = DygraphCanvasRenderer._fastCanvasProxy(ctx); + } + + // For filled charts, we draw points from left to right, then back along + // the x-axis to complete a shape for filling. + // For stacked plots, this "back path" is a more complex shape. This array + // stores the [x, y] values needed to trace that shape. + var pathBack = []; + + // TODO(danvk): there are a lot of options at play in this loop. + // The logic would be much clearer if some (e.g. stackGraph and + // stepPlot) were split off into separate sub-plotters. + var point; + while (iter.hasNext) { + point = iter.next(); + if (!utils.isOK(point.y) && !stepPlot) { + traceBackPath(ctx, prevX, prevYs[1], pathBack); + pathBack = []; + prevX = NaN; + if (point.y_stacked !== null && !isNaN(point.y_stacked)) { + baseline[point.canvasx] = area.h * point.y_stacked + area.y; + } + continue; + } + if (stackedGraph) { + if (!is_first && last_x == point.xval) { + continue; + } else { + is_first = false; + last_x = point.xval; + } + + currBaseline = baseline[point.canvasx]; + var lastY; + if (currBaseline === undefined) { + lastY = axisY; + } else { + if (prevStepPlot) { + lastY = currBaseline[0]; + } else { + lastY = currBaseline; + } + } + newYs = [point.canvasy, lastY]; + + if (stepPlot) { + // Step plots must keep track of the top and bottom of + // the baseline at each point. + if (prevYs[0] === -1) { + baseline[point.canvasx] = [point.canvasy, axisY]; + } else { + baseline[point.canvasx] = [point.canvasy, prevYs[0]]; + } + } else { + baseline[point.canvasx] = point.canvasy; + } + } else { + if (isNaN(point.canvasy) && stepPlot) { + newYs = [area.y + area.h, axisY]; + } else { + newYs = [point.canvasy, axisY]; + } + } + if (!isNaN(prevX)) { + // Move to top fill point + if (stepPlot) { + ctx.lineTo(point.canvasx, prevYs[0]); + ctx.lineTo(point.canvasx, newYs[0]); + } else { + ctx.lineTo(point.canvasx, newYs[0]); + } + + // Record the baseline for the reverse path. + if (stackedGraph) { + pathBack.push([prevX, prevYs[1]]); + if (prevStepPlot && currBaseline) { + // Draw to the bottom of the baseline + pathBack.push([point.canvasx, currBaseline[1]]); + } else { + pathBack.push([point.canvasx, newYs[1]]); + } + } + } else { + ctx.moveTo(point.canvasx, newYs[1]); + ctx.lineTo(point.canvasx, newYs[0]); + } + prevYs = newYs; + prevX = point.canvasx; + } + prevStepPlot = stepPlot; + if (newYs && point) { + traceBackPath(ctx, point.canvasx, newYs[1], pathBack); + pathBack = []; + } + ctx.fill(); + } +}; + +exports['default'] = DygraphCanvasRenderer; +module.exports = exports['default']; + +},{"./dygraph":18,"./dygraph-utils":17}],10:[function(require,module,exports){ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +var _dygraphTickers = require('./dygraph-tickers'); + +var DygraphTickers = _interopRequireWildcard(_dygraphTickers); + +var _dygraphInteractionModel = require('./dygraph-interaction-model'); + +var _dygraphInteractionModel2 = _interopRequireDefault(_dygraphInteractionModel); + +var _dygraphCanvas = require('./dygraph-canvas'); + +var _dygraphCanvas2 = _interopRequireDefault(_dygraphCanvas); + +var _dygraphUtils = require('./dygraph-utils'); + +var utils = _interopRequireWildcard(_dygraphUtils); + +// Default attribute values. +var DEFAULT_ATTRS = { + highlightCircleSize: 3, + highlightSeriesOpts: null, + highlightSeriesBackgroundAlpha: 0.5, + highlightSeriesBackgroundColor: 'rgb(255, 255, 255)', + + labelsSeparateLines: false, + labelsShowZeroValues: true, + labelsKMB: false, + labelsKMG2: false, + showLabelsOnHighlight: true, + + digitsAfterDecimal: 2, + maxNumberWidth: 6, + sigFigs: null, + + strokeWidth: 1.0, + strokeBorderWidth: 0, + strokeBorderColor: "white", + + axisTickSize: 3, + axisLabelFontSize: 14, + rightGap: 5, + + showRoller: false, + xValueParser: undefined, + + delimiter: ',', + + sigma: 2.0, + errorBars: false, + fractions: false, + wilsonInterval: true, // only relevant if fractions is true + customBars: false, + fillGraph: false, + fillAlpha: 0.15, + connectSeparatedPoints: false, + + stackedGraph: false, + stackedGraphNaNFill: 'all', + hideOverlayOnMouseOut: true, + + legend: 'onmouseover', + stepPlot: false, + xRangePad: 0, + yRangePad: null, + drawAxesAtZero: false, + + // Sizes of the various chart labels. + titleHeight: 28, + xLabelHeight: 18, + yLabelWidth: 18, + + axisLineColor: "black", + axisLineWidth: 0.3, + gridLineWidth: 0.3, + axisLabelWidth: 50, + gridLineColor: "rgb(128,128,128)", + + interactionModel: _dygraphInteractionModel2['default'].defaultModel, + animatedZooms: false, // (for now) + + // Range selector options + showRangeSelector: false, + rangeSelectorHeight: 40, + rangeSelectorPlotStrokeColor: "#808FAB", + rangeSelectorPlotFillGradientColor: "white", + rangeSelectorPlotFillColor: "#A7B1C4", + rangeSelectorBackgroundStrokeColor: "gray", + rangeSelectorBackgroundLineWidth: 1, + rangeSelectorPlotLineWidth: 1.5, + rangeSelectorForegroundStrokeColor: "black", + rangeSelectorForegroundLineWidth: 1, + rangeSelectorAlpha: 0.6, + showInRangeSelector: null, + + // The ordering here ensures that central lines always appear above any + // fill bars/error bars. + plotter: [_dygraphCanvas2['default']._fillPlotter, _dygraphCanvas2['default']._errorPlotter, _dygraphCanvas2['default']._linePlotter], + + plugins: [], + + // per-axis options + axes: { + x: { + pixelsPerLabel: 70, + axisLabelWidth: 60, + axisLabelFormatter: utils.dateAxisLabelFormatter, + valueFormatter: utils.dateValueFormatter, + drawGrid: true, + drawAxis: true, + independentTicks: true, + ticker: DygraphTickers.dateTicker + }, + y: { + axisLabelWidth: 50, + pixelsPerLabel: 30, + valueFormatter: utils.numberValueFormatter, + axisLabelFormatter: utils.numberAxisLabelFormatter, + drawGrid: true, + drawAxis: true, + independentTicks: true, + ticker: DygraphTickers.numericTicks + }, + y2: { + axisLabelWidth: 50, + pixelsPerLabel: 30, + valueFormatter: utils.numberValueFormatter, + axisLabelFormatter: utils.numberAxisLabelFormatter, + drawAxis: true, // only applies when there are two axes of data. + drawGrid: false, + independentTicks: false, + ticker: DygraphTickers.numericTicks + } + } +}; + +exports['default'] = DEFAULT_ATTRS; +module.exports = exports['default']; + +},{"./dygraph-canvas":9,"./dygraph-interaction-model":12,"./dygraph-tickers":16,"./dygraph-utils":17}],11:[function(require,module,exports){ +/** + * @license + * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview A wrapper around the Dygraph class which implements the + * interface for a GViz (aka Google Visualization API) visualization. + * It is designed to be a drop-in replacement for Google's AnnotatedTimeline, + * so the documentation at + * http://code.google.com/apis/chart/interactive/docs/gallery/annotatedtimeline.html + * translates over directly. + * + * For a full demo, see: + * - http://dygraphs.com/tests/gviz.html + * - http://dygraphs.com/tests/annotation-gviz.html + */ + +/*global Dygraph:false */ +"use strict"; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _dygraph = require('./dygraph'); + +var _dygraph2 = _interopRequireDefault(_dygraph); + +/** + * A wrapper around Dygraph that implements the gviz API. + * @param {!HTMLDivElement} container The DOM object the visualization should + * live in. + * @constructor + */ +var GVizChart = function GVizChart(container) { + this.container = container; +}; + +/** + * @param {GVizDataTable} data + * @param {Object.<*>} options + */ +GVizChart.prototype.draw = function (data, options) { + // Clear out any existing dygraph. + // TODO(danvk): would it make more sense to simply redraw using the current + // date_graph object? + this.container.innerHTML = ''; + if (typeof this.date_graph != 'undefined') { + this.date_graph.destroy(); + } + + this.date_graph = new _dygraph2['default'](this.container, data, options); +}; + +/** + * Google charts compatible setSelection + * Only row selection is supported, all points in the row will be highlighted + * @param {Array.<{row:number}>} selection_array array of the selected cells + * @public + */ +GVizChart.prototype.setSelection = function (selection_array) { + var row = false; + if (selection_array.length) { + row = selection_array[0].row; + } + this.date_graph.setSelection(row); +}; + +/** + * Google charts compatible getSelection implementation + * @return {Array.<{row:number,column:number}>} array of the selected cells + * @public + */ +GVizChart.prototype.getSelection = function () { + var selection = []; + + var row = this.date_graph.getSelection(); + + if (row < 0) return selection; + + var points = this.date_graph.layout_.points; + for (var setIdx = 0; setIdx < points.length; ++setIdx) { + selection.push({ row: row, column: setIdx + 1 }); + } + + return selection; +}; + +exports['default'] = GVizChart; +module.exports = exports['default']; + +},{"./dygraph":18}],12:[function(require,module,exports){ +/** + * @license + * Copyright 2011 Robert Konigsberg (konigsberg@google.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview The default interaction model for Dygraphs. This is kept out + * of dygraph.js for better navigability. + * @author Robert Konigsberg (konigsberg@google.com) + */ + +/*global Dygraph:false */ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj["default"] = obj; return newObj; } } + +var _dygraphUtils = require('./dygraph-utils'); + +var utils = _interopRequireWildcard(_dygraphUtils); + +/** + * You can drag this many pixels past the edge of the chart and still have it + * be considered a zoom. This makes it easier to zoom to the exact edge of the + * chart, a fairly common operation. + */ +var DRAG_EDGE_MARGIN = 100; + +/** + * A collection of functions to facilitate build custom interaction models. + * @class + */ +var DygraphInteraction = {}; + +/** + * Checks whether the beginning & ending of an event were close enough that it + * should be considered a click. If it should, dispatch appropriate events. + * Returns true if the event was treated as a click. + * + * @param {Event} event + * @param {Dygraph} g + * @param {Object} context + */ +DygraphInteraction.maybeTreatMouseOpAsClick = function (event, g, context) { + context.dragEndX = utils.dragGetX_(event, context); + context.dragEndY = utils.dragGetY_(event, context); + var regionWidth = Math.abs(context.dragEndX - context.dragStartX); + var regionHeight = Math.abs(context.dragEndY - context.dragStartY); + + if (regionWidth < 2 && regionHeight < 2 && g.lastx_ !== undefined && g.lastx_ != -1) { + DygraphInteraction.treatMouseOpAsClick(g, event, context); + } + + context.regionWidth = regionWidth; + context.regionHeight = regionHeight; +}; + +/** + * Called in response to an interaction model operation that + * should start the default panning behavior. + * + * It's used in the default callback for "mousedown" operations. + * Custom interaction model builders can use it to provide the default + * panning behavior. + * + * @param {Event} event the event object which led to the startPan call. + * @param {Dygraph} g The dygraph on which to act. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +DygraphInteraction.startPan = function (event, g, context) { + var i, axis; + context.isPanning = true; + var xRange = g.xAxisRange(); + + if (g.getOptionForAxis("logscale", "x")) { + context.initialLeftmostDate = utils.log10(xRange[0]); + context.dateRange = utils.log10(xRange[1]) - utils.log10(xRange[0]); + } else { + context.initialLeftmostDate = xRange[0]; + context.dateRange = xRange[1] - xRange[0]; + } + context.xUnitsPerPixel = context.dateRange / (g.plotter_.area.w - 1); + + if (g.getNumericOption("panEdgeFraction")) { + var maxXPixelsToDraw = g.width_ * g.getNumericOption("panEdgeFraction"); + var xExtremes = g.xAxisExtremes(); // I REALLY WANT TO CALL THIS xTremes! + + var boundedLeftX = g.toDomXCoord(xExtremes[0]) - maxXPixelsToDraw; + var boundedRightX = g.toDomXCoord(xExtremes[1]) + maxXPixelsToDraw; + + var boundedLeftDate = g.toDataXCoord(boundedLeftX); + var boundedRightDate = g.toDataXCoord(boundedRightX); + context.boundedDates = [boundedLeftDate, boundedRightDate]; + + var boundedValues = []; + var maxYPixelsToDraw = g.height_ * g.getNumericOption("panEdgeFraction"); + + for (i = 0; i < g.axes_.length; i++) { + axis = g.axes_[i]; + var yExtremes = axis.extremeRange; + + var boundedTopY = g.toDomYCoord(yExtremes[0], i) + maxYPixelsToDraw; + var boundedBottomY = g.toDomYCoord(yExtremes[1], i) - maxYPixelsToDraw; + + var boundedTopValue = g.toDataYCoord(boundedTopY, i); + var boundedBottomValue = g.toDataYCoord(boundedBottomY, i); + + boundedValues[i] = [boundedTopValue, boundedBottomValue]; + } + context.boundedValues = boundedValues; + } + + // Record the range of each y-axis at the start of the drag. + // If any axis has a valueRange, then we want a 2D pan. + // We can't store data directly in g.axes_, because it does not belong to us + // and could change out from under us during a pan (say if there's a data + // update). + context.is2DPan = false; + context.axes = []; + for (i = 0; i < g.axes_.length; i++) { + axis = g.axes_[i]; + var axis_data = {}; + var yRange = g.yAxisRange(i); + // TODO(konigsberg): These values should be in |context|. + // In log scale, initialTopValue, dragValueRange and unitsPerPixel are log scale. + var logscale = g.attributes_.getForAxis("logscale", i); + if (logscale) { + axis_data.initialTopValue = utils.log10(yRange[1]); + axis_data.dragValueRange = utils.log10(yRange[1]) - utils.log10(yRange[0]); + } else { + axis_data.initialTopValue = yRange[1]; + axis_data.dragValueRange = yRange[1] - yRange[0]; + } + axis_data.unitsPerPixel = axis_data.dragValueRange / (g.plotter_.area.h - 1); + context.axes.push(axis_data); + + // While calculating axes, set 2dpan. + if (axis.valueRange) context.is2DPan = true; + } +}; + +/** + * Called in response to an interaction model operation that + * responds to an event that pans the view. + * + * It's used in the default callback for "mousemove" operations. + * Custom interaction model builders can use it to provide the default + * panning behavior. + * + * @param {Event} event the event object which led to the movePan call. + * @param {Dygraph} g The dygraph on which to act. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +DygraphInteraction.movePan = function (event, g, context) { + context.dragEndX = utils.dragGetX_(event, context); + context.dragEndY = utils.dragGetY_(event, context); + + var minDate = context.initialLeftmostDate - (context.dragEndX - context.dragStartX) * context.xUnitsPerPixel; + if (context.boundedDates) { + minDate = Math.max(minDate, context.boundedDates[0]); + } + var maxDate = minDate + context.dateRange; + if (context.boundedDates) { + if (maxDate > context.boundedDates[1]) { + // Adjust minDate, and recompute maxDate. + minDate = minDate - (maxDate - context.boundedDates[1]); + maxDate = minDate + context.dateRange; + } + } + + if (g.getOptionForAxis("logscale", "x")) { + g.dateWindow_ = [Math.pow(utils.LOG_SCALE, minDate), Math.pow(utils.LOG_SCALE, maxDate)]; + } else { + g.dateWindow_ = [minDate, maxDate]; + } + + // y-axis scaling is automatic unless this is a full 2D pan. + if (context.is2DPan) { + + var pixelsDragged = context.dragEndY - context.dragStartY; + + // Adjust each axis appropriately. + for (var i = 0; i < g.axes_.length; i++) { + var axis = g.axes_[i]; + var axis_data = context.axes[i]; + var unitsDragged = pixelsDragged * axis_data.unitsPerPixel; + + var boundedValue = context.boundedValues ? context.boundedValues[i] : null; + + // In log scale, maxValue and minValue are the logs of those values. + var maxValue = axis_data.initialTopValue + unitsDragged; + if (boundedValue) { + maxValue = Math.min(maxValue, boundedValue[1]); + } + var minValue = maxValue - axis_data.dragValueRange; + if (boundedValue) { + if (minValue < boundedValue[0]) { + // Adjust maxValue, and recompute minValue. + maxValue = maxValue - (minValue - boundedValue[0]); + minValue = maxValue - axis_data.dragValueRange; + } + } + if (g.attributes_.getForAxis("logscale", i)) { + axis.valueRange = [Math.pow(utils.LOG_SCALE, minValue), Math.pow(utils.LOG_SCALE, maxValue)]; + } else { + axis.valueRange = [minValue, maxValue]; + } + } + } + + g.drawGraph_(false); +}; + +/** + * Called in response to an interaction model operation that + * responds to an event that ends panning. + * + * It's used in the default callback for "mouseup" operations. + * Custom interaction model builders can use it to provide the default + * panning behavior. + * + * @param {Event} event the event object which led to the endPan call. + * @param {Dygraph} g The dygraph on which to act. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +DygraphInteraction.endPan = DygraphInteraction.maybeTreatMouseOpAsClick; + +/** + * Called in response to an interaction model operation that + * responds to an event that starts zooming. + * + * It's used in the default callback for "mousedown" operations. + * Custom interaction model builders can use it to provide the default + * zooming behavior. + * + * @param {Event} event the event object which led to the startZoom call. + * @param {Dygraph} g The dygraph on which to act. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +DygraphInteraction.startZoom = function (event, g, context) { + context.isZooming = true; + context.zoomMoved = false; +}; + +/** + * Called in response to an interaction model operation that + * responds to an event that defines zoom boundaries. + * + * It's used in the default callback for "mousemove" operations. + * Custom interaction model builders can use it to provide the default + * zooming behavior. + * + * @param {Event} event the event object which led to the moveZoom call. + * @param {Dygraph} g The dygraph on which to act. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +DygraphInteraction.moveZoom = function (event, g, context) { + context.zoomMoved = true; + context.dragEndX = utils.dragGetX_(event, context); + context.dragEndY = utils.dragGetY_(event, context); + + var xDelta = Math.abs(context.dragStartX - context.dragEndX); + var yDelta = Math.abs(context.dragStartY - context.dragEndY); + + // drag direction threshold for y axis is twice as large as x axis + context.dragDirection = xDelta < yDelta / 2 ? utils.VERTICAL : utils.HORIZONTAL; + + g.drawZoomRect_(context.dragDirection, context.dragStartX, context.dragEndX, context.dragStartY, context.dragEndY, context.prevDragDirection, context.prevEndX, context.prevEndY); + + context.prevEndX = context.dragEndX; + context.prevEndY = context.dragEndY; + context.prevDragDirection = context.dragDirection; +}; + +/** + * TODO(danvk): move this logic into dygraph.js + * @param {Dygraph} g + * @param {Event} event + * @param {Object} context + */ +DygraphInteraction.treatMouseOpAsClick = function (g, event, context) { + var clickCallback = g.getFunctionOption('clickCallback'); + var pointClickCallback = g.getFunctionOption('pointClickCallback'); + + var selectedPoint = null; + + // Find out if the click occurs on a point. + var closestIdx = -1; + var closestDistance = Number.MAX_VALUE; + + // check if the click was on a particular point. + for (var i = 0; i < g.selPoints_.length; i++) { + var p = g.selPoints_[i]; + var distance = Math.pow(p.canvasx - context.dragEndX, 2) + Math.pow(p.canvasy - context.dragEndY, 2); + if (!isNaN(distance) && (closestIdx == -1 || distance < closestDistance)) { + closestDistance = distance; + closestIdx = i; + } + } + + // Allow any click within two pixels of the dot. + var radius = g.getNumericOption('highlightCircleSize') + 2; + if (closestDistance <= radius * radius) { + selectedPoint = g.selPoints_[closestIdx]; + } + + if (selectedPoint) { + var e = { + cancelable: true, + point: selectedPoint, + canvasx: context.dragEndX, + canvasy: context.dragEndY + }; + var defaultPrevented = g.cascadeEvents_('pointClick', e); + if (defaultPrevented) { + // Note: this also prevents click / clickCallback from firing. + return; + } + if (pointClickCallback) { + pointClickCallback.call(g, event, selectedPoint); + } + } + + var e = { + cancelable: true, + xval: g.lastx_, // closest point by x value + pts: g.selPoints_, + canvasx: context.dragEndX, + canvasy: context.dragEndY + }; + if (!g.cascadeEvents_('click', e)) { + if (clickCallback) { + // TODO(danvk): pass along more info about the points, e.g. 'x' + clickCallback.call(g, event, g.lastx_, g.selPoints_); + } + } +}; + +/** + * Called in response to an interaction model operation that + * responds to an event that performs a zoom based on previously defined + * bounds.. + * + * It's used in the default callback for "mouseup" operations. + * Custom interaction model builders can use it to provide the default + * zooming behavior. + * + * @param {Event} event the event object which led to the endZoom call. + * @param {Dygraph} g The dygraph on which to end the zoom. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +DygraphInteraction.endZoom = function (event, g, context) { + g.clearZoomRect_(); + context.isZooming = false; + DygraphInteraction.maybeTreatMouseOpAsClick(event, g, context); + + // The zoom rectangle is visibly clipped to the plot area, so its behavior + // should be as well. + // See http://code.google.com/p/dygraphs/issues/detail?id=280 + var plotArea = g.getArea(); + if (context.regionWidth >= 10 && context.dragDirection == utils.HORIZONTAL) { + var left = Math.min(context.dragStartX, context.dragEndX), + right = Math.max(context.dragStartX, context.dragEndX); + left = Math.max(left, plotArea.x); + right = Math.min(right, plotArea.x + plotArea.w); + if (left < right) { + g.doZoomX_(left, right); + } + context.cancelNextDblclick = true; + } else if (context.regionHeight >= 10 && context.dragDirection == utils.VERTICAL) { + var top = Math.min(context.dragStartY, context.dragEndY), + bottom = Math.max(context.dragStartY, context.dragEndY); + top = Math.max(top, plotArea.y); + bottom = Math.min(bottom, plotArea.y + plotArea.h); + if (top < bottom) { + g.doZoomY_(top, bottom); + } + context.cancelNextDblclick = true; + } + context.dragStartX = null; + context.dragStartY = null; +}; + +/** + * @private + */ +DygraphInteraction.startTouch = function (event, g, context) { + event.preventDefault(); // touch browsers are all nice. + if (event.touches.length > 1) { + // If the user ever puts two fingers down, it's not a double tap. + context.startTimeForDoubleTapMs = null; + } + + var touches = []; + for (var i = 0; i < event.touches.length; i++) { + var t = event.touches[i]; + // we dispense with 'dragGetX_' because all touchBrowsers support pageX + touches.push({ + pageX: t.pageX, + pageY: t.pageY, + dataX: g.toDataXCoord(t.pageX), + dataY: g.toDataYCoord(t.pageY) + // identifier: t.identifier + }); + } + context.initialTouches = touches; + + if (touches.length == 1) { + // This is just a swipe. + context.initialPinchCenter = touches[0]; + context.touchDirections = { x: true, y: true }; + } else if (touches.length >= 2) { + // It's become a pinch! + // In case there are 3+ touches, we ignore all but the "first" two. + + // only screen coordinates can be averaged (data coords could be log scale). + context.initialPinchCenter = { + pageX: 0.5 * (touches[0].pageX + touches[1].pageX), + pageY: 0.5 * (touches[0].pageY + touches[1].pageY), + + // TODO(danvk): remove + dataX: 0.5 * (touches[0].dataX + touches[1].dataX), + dataY: 0.5 * (touches[0].dataY + touches[1].dataY) + }; + + // Make pinches in a 45-degree swath around either axis 1-dimensional zooms. + var initialAngle = 180 / Math.PI * Math.atan2(context.initialPinchCenter.pageY - touches[0].pageY, touches[0].pageX - context.initialPinchCenter.pageX); + + // use symmetry to get it into the first quadrant. + initialAngle = Math.abs(initialAngle); + if (initialAngle > 90) initialAngle = 90 - initialAngle; + + context.touchDirections = { + x: initialAngle < 90 - 45 / 2, + y: initialAngle > 45 / 2 + }; + } + + // save the full x & y ranges. + context.initialRange = { + x: g.xAxisRange(), + y: g.yAxisRange() + }; +}; + +/** + * @private + */ +DygraphInteraction.moveTouch = function (event, g, context) { + // If the tap moves, then it's definitely not part of a double-tap. + context.startTimeForDoubleTapMs = null; + + var i, + touches = []; + for (i = 0; i < event.touches.length; i++) { + var t = event.touches[i]; + touches.push({ + pageX: t.pageX, + pageY: t.pageY + }); + } + var initialTouches = context.initialTouches; + + var c_now; + + // old and new centers. + var c_init = context.initialPinchCenter; + if (touches.length == 1) { + c_now = touches[0]; + } else { + c_now = { + pageX: 0.5 * (touches[0].pageX + touches[1].pageX), + pageY: 0.5 * (touches[0].pageY + touches[1].pageY) + }; + } + + // this is the "swipe" component + // we toss it out for now, but could use it in the future. + var swipe = { + pageX: c_now.pageX - c_init.pageX, + pageY: c_now.pageY - c_init.pageY + }; + var dataWidth = context.initialRange.x[1] - context.initialRange.x[0]; + var dataHeight = context.initialRange.y[0] - context.initialRange.y[1]; + swipe.dataX = swipe.pageX / g.plotter_.area.w * dataWidth; + swipe.dataY = swipe.pageY / g.plotter_.area.h * dataHeight; + var xScale, yScale; + + // The residual bits are usually split into scale & rotate bits, but we split + // them into x-scale and y-scale bits. + if (touches.length == 1) { + xScale = 1.0; + yScale = 1.0; + } else if (touches.length >= 2) { + var initHalfWidth = initialTouches[1].pageX - c_init.pageX; + xScale = (touches[1].pageX - c_now.pageX) / initHalfWidth; + + var initHalfHeight = initialTouches[1].pageY - c_init.pageY; + yScale = (touches[1].pageY - c_now.pageY) / initHalfHeight; + } + + // Clip scaling to [1/8, 8] to prevent too much blowup. + xScale = Math.min(8, Math.max(0.125, xScale)); + yScale = Math.min(8, Math.max(0.125, yScale)); + + var didZoom = false; + if (context.touchDirections.x) { + g.dateWindow_ = [c_init.dataX - swipe.dataX + (context.initialRange.x[0] - c_init.dataX) / xScale, c_init.dataX - swipe.dataX + (context.initialRange.x[1] - c_init.dataX) / xScale]; + didZoom = true; + } + + if (context.touchDirections.y) { + for (i = 0; i < 1 /*g.axes_.length*/; i++) { + var axis = g.axes_[i]; + var logscale = g.attributes_.getForAxis("logscale", i); + if (logscale) { + // TODO(danvk): implement + } else { + axis.valueRange = [c_init.dataY - swipe.dataY + (context.initialRange.y[0] - c_init.dataY) / yScale, c_init.dataY - swipe.dataY + (context.initialRange.y[1] - c_init.dataY) / yScale]; + didZoom = true; + } + } + } + + g.drawGraph_(false); + + // We only call zoomCallback on zooms, not pans, to mirror desktop behavior. + if (didZoom && touches.length > 1 && g.getFunctionOption('zoomCallback')) { + var viewWindow = g.xAxisRange(); + g.getFunctionOption("zoomCallback").call(g, viewWindow[0], viewWindow[1], g.yAxisRanges()); + } +}; + +/** + * @private + */ +DygraphInteraction.endTouch = function (event, g, context) { + if (event.touches.length !== 0) { + // this is effectively a "reset" + DygraphInteraction.startTouch(event, g, context); + } else if (event.changedTouches.length == 1) { + // Could be part of a "double tap" + // The heuristic here is that it's a double-tap if the two touchend events + // occur within 500ms and within a 50x50 pixel box. + var now = new Date().getTime(); + var t = event.changedTouches[0]; + if (context.startTimeForDoubleTapMs && now - context.startTimeForDoubleTapMs < 500 && context.doubleTapX && Math.abs(context.doubleTapX - t.screenX) < 50 && context.doubleTapY && Math.abs(context.doubleTapY - t.screenY) < 50) { + g.resetZoom(); + } else { + context.startTimeForDoubleTapMs = now; + context.doubleTapX = t.screenX; + context.doubleTapY = t.screenY; + } + } +}; + +// Determine the distance from x to [left, right]. +var distanceFromInterval = function distanceFromInterval(x, left, right) { + if (x < left) { + return left - x; + } else if (x > right) { + return x - right; + } else { + return 0; + } +}; + +/** + * Returns the number of pixels by which the event happens from the nearest + * edge of the chart. For events in the interior of the chart, this returns zero. + */ +var distanceFromChart = function distanceFromChart(event, g) { + var chartPos = utils.findPos(g.canvas_); + var box = { + left: chartPos.x, + right: chartPos.x + g.canvas_.offsetWidth, + top: chartPos.y, + bottom: chartPos.y + g.canvas_.offsetHeight + }; + + var pt = { + x: utils.pageX(event), + y: utils.pageY(event) + }; + + var dx = distanceFromInterval(pt.x, box.left, box.right), + dy = distanceFromInterval(pt.y, box.top, box.bottom); + return Math.max(dx, dy); +}; + +/** + * Default interation model for dygraphs. You can refer to specific elements of + * this when constructing your own interaction model, e.g.: + * g.updateOptions( { + * interactionModel: { + * mousedown: DygraphInteraction.defaultInteractionModel.mousedown + * } + * } ); + */ +DygraphInteraction.defaultModel = { + // Track the beginning of drag events + mousedown: function mousedown(event, g, context) { + // Right-click should not initiate a zoom. + if (event.button && event.button == 2) return; + + context.initializeMouseDown(event, g, context); + + if (event.altKey || event.shiftKey) { + DygraphInteraction.startPan(event, g, context); + } else { + DygraphInteraction.startZoom(event, g, context); + } + + // Note: we register mousemove/mouseup on document to allow some leeway for + // events to move outside of the chart. Interaction model events get + // registered on the canvas, which is too small to allow this. + var mousemove = function mousemove(event) { + if (context.isZooming) { + // When the mouse moves >200px from the chart edge, cancel the zoom. + var d = distanceFromChart(event, g); + if (d < DRAG_EDGE_MARGIN) { + DygraphInteraction.moveZoom(event, g, context); + } else { + if (context.dragEndX !== null) { + context.dragEndX = null; + context.dragEndY = null; + g.clearZoomRect_(); + } + } + } else if (context.isPanning) { + DygraphInteraction.movePan(event, g, context); + } + }; + var mouseup = function mouseup(event) { + if (context.isZooming) { + if (context.dragEndX !== null) { + DygraphInteraction.endZoom(event, g, context); + } else { + DygraphInteraction.maybeTreatMouseOpAsClick(event, g, context); + } + } else if (context.isPanning) { + DygraphInteraction.endPan(event, g, context); + } + + utils.removeEvent(document, 'mousemove', mousemove); + utils.removeEvent(document, 'mouseup', mouseup); + context.destroy(); + }; + + g.addAndTrackEvent(document, 'mousemove', mousemove); + g.addAndTrackEvent(document, 'mouseup', mouseup); + }, + willDestroyContextMyself: true, + + touchstart: function touchstart(event, g, context) { + DygraphInteraction.startTouch(event, g, context); + }, + touchmove: function touchmove(event, g, context) { + DygraphInteraction.moveTouch(event, g, context); + }, + touchend: function touchend(event, g, context) { + DygraphInteraction.endTouch(event, g, context); + }, + + // Disable zooming out if panning. + dblclick: function dblclick(event, g, context) { + if (context.cancelNextDblclick) { + context.cancelNextDblclick = false; + return; + } + + // Give plugins a chance to grab this event. + var e = { + canvasx: context.dragEndX, + canvasy: context.dragEndY, + cancelable: true + }; + if (g.cascadeEvents_('dblclick', e)) { + return; + } + + if (event.altKey || event.shiftKey) { + return; + } + g.resetZoom(); + } +}; + +/* +Dygraph.DEFAULT_ATTRS.interactionModel = DygraphInteraction.defaultModel; + +// old ways of accessing these methods/properties +Dygraph.defaultInteractionModel = DygraphInteraction.defaultModel; +Dygraph.endZoom = DygraphInteraction.endZoom; +Dygraph.moveZoom = DygraphInteraction.moveZoom; +Dygraph.startZoom = DygraphInteraction.startZoom; +Dygraph.endPan = DygraphInteraction.endPan; +Dygraph.movePan = DygraphInteraction.movePan; +Dygraph.startPan = DygraphInteraction.startPan; +*/ + +DygraphInteraction.nonInteractiveModel_ = { + mousedown: function mousedown(event, g, context) { + context.initializeMouseDown(event, g, context); + }, + mouseup: DygraphInteraction.maybeTreatMouseOpAsClick +}; + +// Default interaction model when using the range selector. +DygraphInteraction.dragIsPanInteractionModel = { + mousedown: function mousedown(event, g, context) { + context.initializeMouseDown(event, g, context); + DygraphInteraction.startPan(event, g, context); + }, + mousemove: function mousemove(event, g, context) { + if (context.isPanning) { + DygraphInteraction.movePan(event, g, context); + } + }, + mouseup: function mouseup(event, g, context) { + if (context.isPanning) { + DygraphInteraction.endPan(event, g, context); + } + } +}; + +exports["default"] = DygraphInteraction; +module.exports = exports["default"]; + +},{"./dygraph-utils":17}],13:[function(require,module,exports){ +/** + * @license + * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview Based on PlotKitLayout, but modified to meet the needs of + * dygraphs. + */ + +/*global Dygraph:false */ +"use strict"; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +var _dygraphUtils = require('./dygraph-utils'); + +var utils = _interopRequireWildcard(_dygraphUtils); + +/** + * Creates a new DygraphLayout object. + * + * This class contains all the data to be charted. + * It uses data coordinates, but also records the chart range (in data + * coordinates) and hence is able to calculate percentage positions ('In this + * view, Point A lies 25% down the x-axis.') + * + * Two things that it does not do are: + * 1. Record pixel coordinates for anything. + * 2. (oddly) determine anything about the layout of chart elements. + * + * The naming is a vestige of Dygraph's original PlotKit roots. + * + * @constructor + */ +var DygraphLayout = function DygraphLayout(dygraph) { + this.dygraph_ = dygraph; + /** + * Array of points for each series. + * + * [series index][row index in series] = |Point| structure, + * where series index refers to visible series only, and the + * point index is for the reduced set of points for the current + * zoom region (including one point just outside the window). + * All points in the same row index share the same X value. + * + * @type {Array.>} + */ + this.points = []; + this.setNames = []; + this.annotations = []; + this.yAxes_ = null; + + // TODO(danvk): it's odd that xTicks_ and yTicks_ are inputs, but xticks and + // yticks are outputs. Clean this up. + this.xTicks_ = null; + this.yTicks_ = null; +}; + +/** + * Add points for a single series. + * + * @param {string} setname Name of the series. + * @param {Array.} set_xy Points for the series. + */ +DygraphLayout.prototype.addDataset = function (setname, set_xy) { + this.points.push(set_xy); + this.setNames.push(setname); +}; + +/** + * Returns the box which the chart should be drawn in. This is the canvas's + * box, less space needed for the axis and chart labels. + * + * @return {{x: number, y: number, w: number, h: number}} + */ +DygraphLayout.prototype.getPlotArea = function () { + return this.area_; +}; + +// Compute the box which the chart should be drawn in. This is the canvas's +// box, less space needed for axis, chart labels, and other plug-ins. +// NOTE: This should only be called by Dygraph.predraw_(). +DygraphLayout.prototype.computePlotArea = function () { + var area = { + // TODO(danvk): per-axis setting. + x: 0, + y: 0 + }; + + area.w = this.dygraph_.width_ - area.x - this.dygraph_.getOption('rightGap'); + area.h = this.dygraph_.height_; + + // Let plugins reserve space. + var e = { + chart_div: this.dygraph_.graphDiv, + reserveSpaceLeft: function reserveSpaceLeft(px) { + var r = { + x: area.x, + y: area.y, + w: px, + h: area.h + }; + area.x += px; + area.w -= px; + return r; + }, + reserveSpaceRight: function reserveSpaceRight(px) { + var r = { + x: area.x + area.w - px, + y: area.y, + w: px, + h: area.h + }; + area.w -= px; + return r; + }, + reserveSpaceTop: function reserveSpaceTop(px) { + var r = { + x: area.x, + y: area.y, + w: area.w, + h: px + }; + area.y += px; + area.h -= px; + return r; + }, + reserveSpaceBottom: function reserveSpaceBottom(px) { + var r = { + x: area.x, + y: area.y + area.h - px, + w: area.w, + h: px + }; + area.h -= px; + return r; + }, + chartRect: function chartRect() { + return { x: area.x, y: area.y, w: area.w, h: area.h }; + } + }; + this.dygraph_.cascadeEvents_('layout', e); + + this.area_ = area; +}; + +DygraphLayout.prototype.setAnnotations = function (ann) { + // The Dygraph object's annotations aren't parsed. We parse them here and + // save a copy. If there is no parser, then the user must be using raw format. + this.annotations = []; + var parse = this.dygraph_.getOption('xValueParser') || function (x) { + return x; + }; + for (var i = 0; i < ann.length; i++) { + var a = {}; + if (!ann[i].xval && ann[i].x === undefined) { + console.error("Annotations must have an 'x' property"); + return; + } + if (ann[i].icon && !(ann[i].hasOwnProperty('width') && ann[i].hasOwnProperty('height'))) { + console.error("Must set width and height when setting " + "annotation.icon property"); + return; + } + utils.update(a, ann[i]); + if (!a.xval) a.xval = parse(a.x); + this.annotations.push(a); + } +}; + +DygraphLayout.prototype.setXTicks = function (xTicks) { + this.xTicks_ = xTicks; +}; + +// TODO(danvk): add this to the Dygraph object's API or move it into Layout. +DygraphLayout.prototype.setYAxes = function (yAxes) { + this.yAxes_ = yAxes; +}; + +DygraphLayout.prototype.evaluate = function () { + this._xAxis = {}; + this._evaluateLimits(); + this._evaluateLineCharts(); + this._evaluateLineTicks(); + this._evaluateAnnotations(); +}; + +DygraphLayout.prototype._evaluateLimits = function () { + var xlimits = this.dygraph_.xAxisRange(); + this._xAxis.minval = xlimits[0]; + this._xAxis.maxval = xlimits[1]; + var xrange = xlimits[1] - xlimits[0]; + this._xAxis.scale = xrange !== 0 ? 1 / xrange : 1.0; + + if (this.dygraph_.getOptionForAxis("logscale", 'x')) { + this._xAxis.xlogrange = utils.log10(this._xAxis.maxval) - utils.log10(this._xAxis.minval); + this._xAxis.xlogscale = this._xAxis.xlogrange !== 0 ? 1.0 / this._xAxis.xlogrange : 1.0; + } + for (var i = 0; i < this.yAxes_.length; i++) { + var axis = this.yAxes_[i]; + axis.minyval = axis.computedValueRange[0]; + axis.maxyval = axis.computedValueRange[1]; + axis.yrange = axis.maxyval - axis.minyval; + axis.yscale = axis.yrange !== 0 ? 1.0 / axis.yrange : 1.0; + + if (this.dygraph_.getOption("logscale")) { + axis.ylogrange = utils.log10(axis.maxyval) - utils.log10(axis.minyval); + axis.ylogscale = axis.ylogrange !== 0 ? 1.0 / axis.ylogrange : 1.0; + if (!isFinite(axis.ylogrange) || isNaN(axis.ylogrange)) { + console.error('axis ' + i + ' of graph at ' + axis.g + ' can\'t be displayed in log scale for range [' + axis.minyval + ' - ' + axis.maxyval + ']'); + } + } + } +}; + +DygraphLayout.calcXNormal_ = function (value, xAxis, logscale) { + if (logscale) { + return (utils.log10(value) - utils.log10(xAxis.minval)) * xAxis.xlogscale; + } else { + return (value - xAxis.minval) * xAxis.scale; + } +}; + +/** + * @param {DygraphAxisType} axis + * @param {number} value + * @param {boolean} logscale + * @return {number} + */ +DygraphLayout.calcYNormal_ = function (axis, value, logscale) { + if (logscale) { + var x = 1.0 - (utils.log10(value) - utils.log10(axis.minyval)) * axis.ylogscale; + return isFinite(x) ? x : NaN; // shim for v8 issue; see pull request 276 + } else { + return 1.0 - (value - axis.minyval) * axis.yscale; + } +}; + +DygraphLayout.prototype._evaluateLineCharts = function () { + var isStacked = this.dygraph_.getOption("stackedGraph"); + var isLogscaleForX = this.dygraph_.getOptionForAxis("logscale", 'x'); + + for (var setIdx = 0; setIdx < this.points.length; setIdx++) { + var points = this.points[setIdx]; + var setName = this.setNames[setIdx]; + var connectSeparated = this.dygraph_.getOption('connectSeparatedPoints', setName); + var axis = this.dygraph_.axisPropertiesForSeries(setName); + // TODO (konigsberg): use optionsForAxis instead. + var logscale = this.dygraph_.attributes_.getForSeries("logscale", setName); + + for (var j = 0; j < points.length; j++) { + var point = points[j]; + + // Range from 0-1 where 0 represents left and 1 represents right. + point.x = DygraphLayout.calcXNormal_(point.xval, this._xAxis, isLogscaleForX); + // Range from 0-1 where 0 represents top and 1 represents bottom + var yval = point.yval; + if (isStacked) { + point.y_stacked = DygraphLayout.calcYNormal_(axis, point.yval_stacked, logscale); + if (yval !== null && !isNaN(yval)) { + yval = point.yval_stacked; + } + } + if (yval === null) { + yval = NaN; + if (!connectSeparated) { + point.yval = NaN; + } + } + point.y = DygraphLayout.calcYNormal_(axis, yval, logscale); + } + + this.dygraph_.dataHandler_.onLineEvaluated(points, axis, logscale); + } +}; + +DygraphLayout.prototype._evaluateLineTicks = function () { + var i, tick, label, pos, v, has_tick; + this.xticks = []; + for (i = 0; i < this.xTicks_.length; i++) { + tick = this.xTicks_[i]; + label = tick.label; + has_tick = !('label_v' in tick); + v = has_tick ? tick.v : tick.label_v; + pos = this.dygraph_.toPercentXCoord(v); + if (pos >= 0.0 && pos < 1.0) { + this.xticks.push({ pos: pos, label: label, has_tick: has_tick }); + } + } + + this.yticks = []; + for (i = 0; i < this.yAxes_.length; i++) { + var axis = this.yAxes_[i]; + for (var j = 0; j < axis.ticks.length; j++) { + tick = axis.ticks[j]; + label = tick.label; + has_tick = !('label_v' in tick); + v = has_tick ? tick.v : tick.label_v; + pos = this.dygraph_.toPercentYCoord(v, i); + if (pos > 0.0 && pos <= 1.0) { + this.yticks.push({ axis: i, pos: pos, label: label, has_tick: has_tick }); + } + } + } +}; + +DygraphLayout.prototype._evaluateAnnotations = function () { + // Add the annotations to the point to which they belong. + // Make a map from (setName, xval) to annotation for quick lookups. + var i; + var annotations = {}; + for (i = 0; i < this.annotations.length; i++) { + var a = this.annotations[i]; + annotations[a.xval + "," + a.series] = a; + } + + this.annotated_points = []; + + // Exit the function early if there are no annotations. + if (!this.annotations || !this.annotations.length) { + return; + } + + // TODO(antrob): loop through annotations not points. + for (var setIdx = 0; setIdx < this.points.length; setIdx++) { + var points = this.points[setIdx]; + for (i = 0; i < points.length; i++) { + var p = points[i]; + var k = p.xval + "," + p.name; + if (k in annotations) { + p.annotation = annotations[k]; + this.annotated_points.push(p); + } + } + } +}; + +/** + * Convenience function to remove all the data sets from a graph + */ +DygraphLayout.prototype.removeAllDatasets = function () { + delete this.points; + delete this.setNames; + delete this.setPointsLengths; + delete this.setPointsOffsets; + this.points = []; + this.setNames = []; + this.setPointsLengths = []; + this.setPointsOffsets = []; +}; + +exports['default'] = DygraphLayout; +module.exports = exports['default']; + +},{"./dygraph-utils":17}],14:[function(require,module,exports){ +(function (process){ +/** + * @license + * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +"use strict"; + +Object.defineProperty(exports, '__esModule', { + value: true +}); +var OPTIONS_REFERENCE = null; + +// For "production" code, this gets removed by uglifyjs. +if (typeof process !== 'undefined') { + if ("development" != 'production') { + + // NOTE: in addition to parsing as JS, this snippet is expected to be valid + // JSON. This assumption cannot be checked in JS, but it will be checked when + // documentation is generated by the generate-documentation.py script. For the + // most part, this just means that you should always use double quotes. + OPTIONS_REFERENCE = // + { + "xValueParser": { + "default": "parseFloat() or Date.parse()*", + "labels": ["CSV parsing"], + "type": "function(str) -> number", + "description": "A function which parses x-values (i.e. the dependent series). Must return a number, even when the values are dates. In this case, millis since epoch are used. This is used primarily for parsing CSV data. *=Dygraphs is slightly more accepting in the dates which it will parse. See code for details." + }, + "stackedGraph": { + "default": "false", + "labels": ["Data Line display"], + "type": "boolean", + "description": "If set, stack series on top of one another rather than drawing them independently. The first series specified in the input data will wind up on top of the chart and the last will be on bottom. NaN values are drawn as white areas without a line on top, see stackedGraphNaNFill for details." + }, + "stackedGraphNaNFill": { + "default": "all", + "labels": ["Data Line display"], + "type": "string", + "description": "Controls handling of NaN values inside a stacked graph. NaN values are interpolated/extended for stacking purposes, but the actual point value remains NaN in the legend display. Valid option values are \"all\" (interpolate internally, repeat leftmost and rightmost value as needed), \"inside\" (interpolate internally only, use zero outside leftmost and rightmost value), and \"none\" (treat NaN as zero everywhere)." + }, + "pointSize": { + "default": "1", + "labels": ["Data Line display"], + "type": "integer", + "description": "The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is \"isolated\", i.e. there is a missing point on either side of it. This also controls the size of those dots." + }, + "drawPoints": { + "default": "false", + "labels": ["Data Line display"], + "type": "boolean", + "description": "Draw a small dot at each point, in addition to a line going through the point. This makes the individual data points easier to see, but can increase visual clutter in the chart. The small dot can be replaced with a custom rendering by supplying a drawPointCallback." + }, + "drawGapEdgePoints": { + "default": "false", + "labels": ["Data Line display"], + "type": "boolean", + "description": "Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities." + }, + "drawPointCallback": { + "default": "null", + "labels": ["Data Line display"], + "type": "function(g, seriesName, canvasContext, cx, cy, color, pointSize)", + "parameters": [["g", "the reference graph"], ["seriesName", "the name of the series"], ["canvasContext", "the canvas to draw on"], ["cx", "center x coordinate"], ["cy", "center y coordinate"], ["color", "series color"], ["pointSize", "the radius of the image."], ["idx", "the row-index of the point in the data."]], + "description": "Draw a custom item when drawPoints is enabled. Default is a small dot matching the series color. This method should constrain drawing to within pointSize pixels from (cx, cy). Also see drawHighlightPointCallback" + }, + "height": { + "default": "320", + "labels": ["Overall display"], + "type": "integer", + "description": "Height, in pixels, of the chart. If the container div has been explicitly sized, this will be ignored." + }, + "zoomCallback": { + "default": "null", + "labels": ["Callbacks"], + "type": "function(minDate, maxDate, yRanges)", + "parameters": [["minDate", "milliseconds since epoch"], ["maxDate", "milliseconds since epoch."], ["yRanges", "is an array of [bottom, top] pairs, one for each y-axis."]], + "description": "A function to call when the zoom window is changed (either by zooming in or out). When animatedZooms is set, zoomCallback is called once at the end of the transition (it will not be called for intermediate frames)." + }, + "pointClickCallback": { + "snippet": "function(e, point){
  alert(point);
}", + "default": "null", + "labels": ["Callbacks", "Interactive Elements"], + "type": "function(e, point)", + "parameters": [["e", "the event object for the click"], ["point", "the point that was clicked See Point properties for details"]], + "description": "A function to call when a data point is clicked. and the point that was clicked." + }, + "color": { + "default": "(see description)", + "labels": ["Data Series Colors"], + "type": "string", + "example": "red", + "description": "A per-series color definition. Used in conjunction with, and overrides, the colors option." + }, + "colors": { + "default": "(see description)", + "labels": ["Data Series Colors"], + "type": "array", + "example": "['red', '#00FF00']", + "description": "List of colors for the data series. These can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\", etc. If not specified, equally-spaced points around a color wheel are used. Overridden by the 'color' option." + }, + "connectSeparatedPoints": { + "default": "false", + "labels": ["Data Line display"], + "type": "boolean", + "description": "Usually, when Dygraphs encounters a missing value in a data series, it interprets this as a gap and draws it as such. If, instead, the missing values represents an x-value for which only a different series has data, then you'll want to connect the dots by setting this to true. To explicitly include a gap with this option set, use a value of NaN." + }, + "highlightCallback": { + "default": "null", + "labels": ["Callbacks"], + "type": "function(event, x, points, row, seriesName)", + "description": "When set, this callback gets called every time a new point is highlighted.", + "parameters": [["event", "the JavaScript mousemove event"], ["x", "the x-coordinate of the highlighted points"], ["points", "an array of highlighted points: [ {name: 'series', yval: y-value}, … ]"], ["row", "integer index of the highlighted row in the data table, starting from 0"], ["seriesName", "name of the highlighted series, only present if highlightSeriesOpts is set."]] + }, + "drawHighlightPointCallback": { + "default": "null", + "labels": ["Data Line display"], + "type": "function(g, seriesName, canvasContext, cx, cy, color, pointSize)", + "parameters": [["g", "the reference graph"], ["seriesName", "the name of the series"], ["canvasContext", "the canvas to draw on"], ["cx", "center x coordinate"], ["cy", "center y coordinate"], ["color", "series color"], ["pointSize", "the radius of the image."], ["idx", "the row-index of the point in the data."]], + "description": "Draw a custom item when a point is highlighted. Default is a small dot matching the series color. This method should constrain drawing to within pointSize pixels from (cx, cy) Also see drawPointCallback" + }, + "highlightSeriesOpts": { + "default": "null", + "labels": ["Interactive Elements"], + "type": "Object", + "description": "When set, the options from this object are applied to the timeseries closest to the mouse pointer for interactive highlighting. See also 'highlightCallback'. Example: highlightSeriesOpts: { strokeWidth: 3 }." + }, + "highlightSeriesBackgroundAlpha": { + "default": "0.5", + "labels": ["Interactive Elements"], + "type": "float", + "description": "Fade the background while highlighting series. 1=fully visible background (disable fading), 0=hiddden background (show highlighted series only)." + }, + "highlightSeriesBackgroundColor": { + "default": "rgb(255, 255, 255)", + "labels": ["Interactive Elements"], + "type": "string", + "description": "Sets the background color used to fade out the series in conjunction with 'highlightSeriesBackgroundAlpha'." + }, + "includeZero": { + "default": "false", + "labels": ["Axis display"], + "type": "boolean", + "description": "Usually, dygraphs will use the range of the data plus some padding to set the range of the y-axis. If this option is set, the y-axis will always include zero, typically as the lowest value. This can be used to avoid exaggerating the variance in the data" + }, + "rollPeriod": { + "default": "1", + "labels": ["Error Bars", "Rolling Averages"], + "type": "integer >= 1", + "description": "Number of days over which to average data. Discussed extensively above." + }, + "unhighlightCallback": { + "default": "null", + "labels": ["Callbacks"], + "type": "function(event)", + "parameters": [["event", "the mouse event"]], + "description": "When set, this callback gets called every time the user stops highlighting any point by mousing out of the graph." + }, + "axisTickSize": { + "default": "3.0", + "labels": ["Axis display"], + "type": "number", + "description": "The size of the line to display next to each tick mark on x- or y-axes." + }, + "labelsSeparateLines": { + "default": "false", + "labels": ["Legend"], + "type": "boolean", + "description": "Put <br/> between lines in the label string. Often used in conjunction with labelsDiv." + }, + "valueFormatter": { + "default": "Depends on the type of your data.", + "labels": ["Legend", "Value display/formatting"], + "type": "function(num or millis, opts, seriesName, dygraph, row, col)", + "description": "Function to provide a custom display format for the values displayed on mouseover. This does not affect the values that appear on tick marks next to the axes. To format those, see axisLabelFormatter. This is usually set on a per-axis basis. .", + "parameters": [["num_or_millis", "The value to be formatted. This is always a number. For date axes, it's millis since epoch. You can call new Date(millis) to get a Date object."], ["opts", "This is a function you can call to access various options (e.g. opts('labelsKMB')). It returns per-axis values for the option when available."], ["seriesName", "The name of the series from which the point came, e.g. 'X', 'Y', 'A', etc."], ["dygraph", "The dygraph object for which the formatting is being done"], ["row", "The row of the data from which this point comes. g.getValue(row, 0) will return the x-value for this point."], ["col", "The column of the data from which this point comes. g.getValue(row, col) will return the original y-value for this point. This can be used to get the full confidence interval for the point, or access un-rolled values for the point."]] + }, + "annotationMouseOverHandler": { + "default": "null", + "labels": ["Annotations"], + "type": "function(annotation, point, dygraph, event)", + "description": "If provided, this function is called whenever the user mouses over an annotation." + }, + "annotationMouseOutHandler": { + "default": "null", + "labels": ["Annotations"], + "type": "function(annotation, point, dygraph, event)", + "parameters": [["annotation", "the annotation left"], ["point", "the point associated with the annotation"], ["dygraph", "the reference graph"], ["event", "the mouse event"]], + "description": "If provided, this function is called whenever the user mouses out of an annotation." + }, + "annotationClickHandler": { + "default": "null", + "labels": ["Annotations"], + "type": "function(annotation, point, dygraph, event)", + "parameters": [["annotation", "the annotation left"], ["point", "the point associated with the annotation"], ["dygraph", "the reference graph"], ["event", "the mouse event"]], + "description": "If provided, this function is called whenever the user clicks on an annotation." + }, + "annotationDblClickHandler": { + "default": "null", + "labels": ["Annotations"], + "type": "function(annotation, point, dygraph, event)", + "parameters": [["annotation", "the annotation left"], ["point", "the point associated with the annotation"], ["dygraph", "the reference graph"], ["event", "the mouse event"]], + "description": "If provided, this function is called whenever the user double-clicks on an annotation." + }, + "drawCallback": { + "default": "null", + "labels": ["Callbacks"], + "type": "function(dygraph, is_initial)", + "parameters": [["dygraph", "The graph being drawn"], ["is_initial", "True if this is the initial draw, false for subsequent draws."]], + "description": "When set, this callback gets called every time the dygraph is drawn. This includes the initial draw, after zooming and repeatedly while panning." + }, + "labelsKMG2": { + "default": "false", + "labels": ["Value display/formatting"], + "type": "boolean", + "description": "Show k/M/G for kilo/Mega/Giga on y-axis. This is different than labelsKMB in that it uses base 2, not 10." + }, + "delimiter": { + "default": ",", + "labels": ["CSV parsing"], + "type": "string", + "description": "The delimiter to look for when separating fields of a CSV file. Setting this to a tab is not usually necessary, since tab-delimited data is auto-detected." + }, + "axisLabelFontSize": { + "default": "14", + "labels": ["Axis display"], + "type": "integer", + "description": "Size of the font (in pixels) to use in the axis labels, both x- and y-axis." + }, + "underlayCallback": { + "default": "null", + "labels": ["Callbacks"], + "type": "function(context, area, dygraph)", + "parameters": [["context", "the canvas drawing context on which to draw"], ["area", "An object with {x,y,w,h} properties describing the drawing area."], ["dygraph", "the reference graph"]], + "description": "When set, this callback gets called before the chart is drawn. It details on how to use this." + }, + "width": { + "default": "480", + "labels": ["Overall display"], + "type": "integer", + "description": "Width, in pixels, of the chart. If the container div has been explicitly sized, this will be ignored." + }, + "pixelRatio": { + "default": "(devicePixelRatio / context.backingStoreRatio)", + "labels": ["Overall display"], + "type": "float", + "description": "Overrides the pixel ratio scaling factor for the canvas's 2d context. Ordinarily, this is set to the devicePixelRatio / (context.backingStoreRatio || 1), so on mobile devices, where the devicePixelRatio can be somewhere around 3, performance can be improved by overriding this value to something less precise, like 1, at the expense of resolution." + }, + "interactionModel": { + "default": "...", + "labels": ["Interactive Elements"], + "type": "Object", + "description": "TODO(konigsberg): document this" + }, + "ticker": { + "default": "Dygraph.dateTicker or Dygraph.numericTicks", + "labels": ["Axis display"], + "type": "function(min, max, pixels, opts, dygraph, vals) -> [{v: ..., label: ...}, ...]", + "parameters": [["min", ""], ["max", ""], ["pixels", ""], ["opts", ""], ["dygraph", "the reference graph"], ["vals", ""]], + "description": "This lets you specify an arbitrary function to generate tick marks on an axis. The tick marks are an array of (value, label) pairs. The built-in functions go to great lengths to choose good tick marks so, if you set this option, you'll most likely want to call one of them and modify the result. See dygraph-tickers.js for an extensive discussion. This is set on a per-axis basis." + }, + "xAxisHeight": { + "default": "(null)", + "labels": ["Axis display"], + "type": "integer", + "description": "Height, in pixels, of the x-axis. If not set explicitly, this is computed based on axisLabelFontSize and axisTickSize." + }, + "showLabelsOnHighlight": { + "default": "true", + "labels": ["Interactive Elements", "Legend"], + "type": "boolean", + "description": "Whether to show the legend upon mouseover." + }, + "axis": { + "default": "(none)", + "labels": ["Axis display"], + "type": "string", + "description": "Set to either 'y1' or 'y2' to assign a series to a y-axis (primary or secondary). Must be set per-series." + }, + "pixelsPerLabel": { + "default": "70 (x-axis) or 30 (y-axes)", + "labels": ["Axis display", "Grid"], + "type": "integer", + "description": "Number of pixels to require between each x- and y-label. Larger values will yield a sparser axis with fewer ticks. This is set on a per-axis basis." + }, + "labelsDiv": { + "default": "null", + "labels": ["Legend"], + "type": "DOM element or string", + "example": "document.getElementById('foo')or'foo'", + "description": "Show data labels in an external div, rather than on the graph. This value can either be a div element or a div id." + }, + "fractions": { + "default": "false", + "labels": ["CSV parsing", "Error Bars"], + "type": "boolean", + "description": "When set, attempt to parse each cell in the CSV file as \"a/b\", where a and b are integers. The ratio will be plotted. This allows computation of Wilson confidence intervals (see below)." + }, + "logscale": { + "default": "false", + "labels": ["Axis display"], + "type": "boolean", + "description": "When set for the y-axis or x-axis, the graph shows that axis in log scale. Any values less than or equal to zero are not displayed. Showing log scale with ranges that go below zero will result in an unviewable graph.\n\n Not compatible with showZero. connectSeparatedPoints is ignored. This is ignored for date-based x-axes." + }, + "strokeWidth": { + "default": "1.0", + "labels": ["Data Line display"], + "type": "float", + "example": "0.5, 2.0", + "description": "The width of the lines connecting data points. This can be used to increase the contrast or some graphs." + }, + "strokePattern": { + "default": "null", + "labels": ["Data Line display"], + "type": "array", + "example": "[10, 2, 5, 2]", + "description": "A custom pattern array where the even index is a draw and odd is a space in pixels. If null then it draws a solid line. The array should have a even length as any odd lengthed array could be expressed as a smaller even length array. This is used to create dashed lines." + }, + "strokeBorderWidth": { + "default": "null", + "labels": ["Data Line display"], + "type": "float", + "example": "1.0", + "description": "Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines." + }, + "strokeBorderColor": { + "default": "white", + "labels": ["Data Line display"], + "type": "string", + "example": "red, #ccffdd", + "description": "Color for the line border used if strokeBorderWidth is set." + }, + "wilsonInterval": { + "default": "true", + "labels": ["Error Bars"], + "type": "boolean", + "description": "Use in conjunction with the \"fractions\" option. Instead of plotting +/- N standard deviations, dygraphs will compute a Wilson confidence interval and plot that. This has more reasonable behavior for ratios close to 0 or 1." + }, + "fillGraph": { + "default": "false", + "labels": ["Data Line display"], + "type": "boolean", + "description": "Should the area underneath the graph be filled? This option is not compatible with error bars. This may be set on a per-series basis." + }, + "highlightCircleSize": { + "default": "3", + "labels": ["Interactive Elements"], + "type": "integer", + "description": "The size in pixels of the dot drawn over highlighted points." + }, + "gridLineColor": { + "default": "rgb(128,128,128)", + "labels": ["Grid"], + "type": "red, blue", + "description": "The color of the gridlines. This may be set on a per-axis basis to define each axis' grid separately." + }, + "gridLinePattern": { + "default": "null", + "labels": ["Grid"], + "type": "array", + "example": "[10, 2, 5, 2]", + "description": "A custom pattern array where the even index is a draw and odd is a space in pixels. If null then it draws a solid line. The array should have a even length as any odd lengthed array could be expressed as a smaller even length array. This is used to create dashed gridlines." + }, + "visibility": { + "default": "[true, true, ...]", + "labels": ["Data Line display"], + "type": "Array of booleans", + "description": "Which series should initially be visible? Once the Dygraph has been constructed, you can access and modify the visibility of each series using the visibility and setVisibility methods." + }, + "valueRange": { + "default": "Full range of the input is shown", + "labels": ["Axis display"], + "type": "Array of two numbers", + "example": "[10, 110]", + "description": "Explicitly set the vertical range of the graph to [low, high]. This may be set on a per-axis basis to define each y-axis separately. If either limit is unspecified, it will be calculated automatically (e.g. [null, 30] to automatically calculate just the lower bound)" + }, + "colorSaturation": { + "default": "1.0", + "labels": ["Data Series Colors"], + "type": "float (0.0 - 1.0)", + "description": "If colors is not specified, saturation of the automatically-generated data series colors." + }, + "hideOverlayOnMouseOut": { + "default": "true", + "labels": ["Interactive Elements", "Legend"], + "type": "boolean", + "description": "Whether to hide the legend when the mouse leaves the chart area." + }, + "legend": { + "default": "onmouseover", + "labels": ["Legend"], + "type": "string", + "description": "When to display the legend. By default, it only appears when a user mouses over the chart. Set it to \"always\" to always display a legend of some sort. When set to \"follow\", legend follows highlighted points." + }, + "legendFormatter": { + "default": "null", + "labels": ["Legend"], + "type": "function(data): string", + "params": [["data", "An object containing information about the selection (or lack of a selection). This includes formatted values and series information. See here for sample values."]], + "description": "Set this to supply a custom formatter for the legend. See this comment and the legendFormatter demo for usage." + }, + "labelsShowZeroValues": { + "default": "true", + "labels": ["Legend"], + "type": "boolean", + "description": "Show zero value labels in the labelsDiv." + }, + "stepPlot": { + "default": "false", + "labels": ["Data Line display"], + "type": "boolean", + "description": "When set, display the graph as a step plot instead of a line plot. This option may either be set for the whole graph or for single series." + }, + "labelsUTC": { + "default": "false", + "labels": ["Value display/formatting", "Axis display"], + "type": "boolean", + "description": "Show date/time labels according to UTC (instead of local time)." + }, + "labelsKMB": { + "default": "false", + "labels": ["Value display/formatting"], + "type": "boolean", + "description": "Show K/M/B for thousands/millions/billions on y-axis." + }, + "rightGap": { + "default": "5", + "labels": ["Overall display"], + "type": "integer", + "description": "Number of pixels to leave blank at the right edge of the Dygraph. This makes it easier to highlight the right-most data point." + }, + "drawAxesAtZero": { + "default": "false", + "labels": ["Axis display"], + "type": "boolean", + "description": "When set, draw the X axis at the Y=0 position and the Y axis at the X=0 position if those positions are inside the graph's visible area. Otherwise, draw the axes at the bottom or left graph edge as usual." + }, + "xRangePad": { + "default": "0", + "labels": ["Axis display"], + "type": "float", + "description": "Add the specified amount of extra space (in pixels) around the X-axis value range to ensure points at the edges remain visible." + }, + "yRangePad": { + "default": "null", + "labels": ["Axis display"], + "type": "float", + "description": "If set, add the specified amount of extra space (in pixels) around the Y-axis value range to ensure points at the edges remain visible. If unset, use the traditional Y padding algorithm." + }, + "axisLabelFormatter": { + "default": "Depends on the data type", + "labels": ["Axis display"], + "type": "function(number or Date, granularity, opts, dygraph)", + "parameters": [["number or date", "Either a number (for a numeric axis) or a Date object (for a date axis)"], ["granularity", "specifies how fine-grained the axis is. For date axes, this is a reference to the time granularity enumeration, defined in dygraph-tickers.js, e.g. Dygraph.WEEKLY."], ["opts", "a function which provides access to various options on the dygraph, e.g. opts('labelsKMB')."], ["dygraph", "the referenced graph"]], + "description": "Function to call to format the tick values that appear along an axis. This is usually set on a per-axis basis." + }, + "clickCallback": { + "snippet": "function(e, date_millis){
  alert(new Date(date_millis));
}", + "default": "null", + "labels": ["Callbacks"], + "type": "function(e, x, points)", + "parameters": [["e", "The event object for the click"], ["x", "The x value that was clicked (for dates, this is milliseconds since epoch)"], ["points", "The closest points along that date. See Point properties for details."]], + "description": "A function to call when the canvas is clicked." + }, + "labels": { + "default": "[\"X\", \"Y1\", \"Y2\", ...]*", + "labels": ["Legend"], + "type": "array", + "description": "A name for each data series, including the independent (X) series. For CSV files and DataTable objections, this is determined by context. For raw data, this must be specified. If it is not, default values are supplied and a warning is logged." + }, + "dateWindow": { + "default": "Full range of the input is shown", + "labels": ["Axis display"], + "type": "Array of two numbers", + "example": "[
  Date.parse('2006-01-01'),
  (new Date()).valueOf()
]", + "description": "Initially zoom in on a section of the graph. Is of the form [earliest, latest], where earliest/latest are milliseconds since epoch. If the data for the x-axis is numeric, the values in dateWindow must also be numbers." + }, + "showRoller": { + "default": "false", + "labels": ["Interactive Elements", "Rolling Averages"], + "type": "boolean", + "description": "If the rolling average period text box should be shown." + }, + "sigma": { + "default": "2.0", + "labels": ["Error Bars"], + "type": "float", + "description": "When errorBars is set, shade this many standard deviations above/below each point." + }, + "customBars": { + "default": "false", + "labels": ["CSV parsing", "Error Bars"], + "type": "boolean", + "description": "When set, parse each CSV cell as \"low;middle;high\". Error bars will be drawn for each point between low and high, with the series itself going through middle." + }, + "colorValue": { + "default": "1.0", + "labels": ["Data Series Colors"], + "type": "float (0.0 - 1.0)", + "description": "If colors is not specified, value of the data series colors, as in hue/saturation/value. (0.0-1.0, default 0.5)" + }, + "errorBars": { + "default": "false", + "labels": ["CSV parsing", "Error Bars"], + "type": "boolean", + "description": "Does the data contain standard deviations? Setting this to true alters the input format (see above)." + }, + "displayAnnotations": { + "default": "false", + "labels": ["Annotations"], + "type": "boolean", + "description": "Only applies when Dygraphs is used as a GViz chart. Causes string columns following a data series to be interpreted as annotations on points in that series. This is the same format used by Google's AnnotatedTimeLine chart." + }, + "panEdgeFraction": { + "default": "null", + "labels": ["Axis display", "Interactive Elements"], + "type": "float", + "description": "A value representing the farthest a graph may be panned, in percent of the display. For example, a value of 0.1 means that the graph can only be panned 10% passed the edges of the displayed values. null means no bounds." + }, + "title": { + "labels": ["Chart labels"], + "type": "string", + "default": "null", + "description": "Text to display above the chart. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-title' classes." + }, + "titleHeight": { + "default": "18", + "labels": ["Chart labels"], + "type": "integer", + "description": "Height of the chart title, in pixels. This also controls the default font size of the title. If you style the title on your own, this controls how much space is set aside above the chart for the title's div." + }, + "xlabel": { + "labels": ["Chart labels"], + "type": "string", + "default": "null", + "description": "Text to display below the chart's x-axis. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-xlabel' classes." + }, + "xLabelHeight": { + "labels": ["Chart labels"], + "type": "integer", + "default": "18", + "description": "Height of the x-axis label, in pixels. This also controls the default font size of the x-axis label. If you style the label on your own, this controls how much space is set aside below the chart for the x-axis label's div." + }, + "ylabel": { + "labels": ["Chart labels"], + "type": "string", + "default": "null", + "description": "Text to display to the left of the chart's y-axis. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-ylabel' classes. The text will be rotated 90 degrees by default, so CSS rules may behave in unintuitive ways. No additional space is set aside for a y-axis label. If you need more space, increase the width of the y-axis tick labels using the yAxisLabelWidth option. If you need a wider div for the y-axis label, either style it that way with CSS (but remember that it's rotated, so width is controlled by the 'height' property) or set the yLabelWidth option." + }, + "y2label": { + "labels": ["Chart labels"], + "type": "string", + "default": "null", + "description": "Text to display to the right of the chart's secondary y-axis. This label is only displayed if a secondary y-axis is present. See this test for an example of how to do this. The comments for the 'ylabel' option generally apply here as well. This label gets a 'dygraph-y2label' instead of a 'dygraph-ylabel' class." + }, + "yLabelWidth": { + "labels": ["Chart labels"], + "type": "integer", + "default": "18", + "description": "Width of the div which contains the y-axis label. Since the y-axis label appears rotated 90 degrees, this actually affects the height of its div." + }, + "drawGrid": { + "default": "true for x and y, false for y2", + "labels": ["Grid"], + "type": "boolean", + "description": "Whether to display gridlines in the chart. This may be set on a per-axis basis to define the visibility of each axis' grid separately." + }, + "independentTicks": { + "default": "true for y, false for y2", + "labels": ["Axis display", "Grid"], + "type": "boolean", + "description": "Only valid for y and y2, has no effect on x: This option defines whether the y axes should align their ticks or if they should be independent. Possible combinations: 1.) y=true, y2=false (default): y is the primary axis and the y2 ticks are aligned to the the ones of y. (only 1 grid) 2.) y=false, y2=true: y2 is the primary axis and the y ticks are aligned to the the ones of y2. (only 1 grid) 3.) y=true, y2=true: Both axis are independent and have their own ticks. (2 grids) 4.) y=false, y2=false: Invalid configuration causes an error." + }, + "drawAxis": { + "default": "true for x and y, false for y2", + "labels": ["Axis display"], + "type": "boolean", + "description": "Whether to draw the specified axis. This may be set on a per-axis basis to define the visibility of each axis separately. Setting this to false also prevents axis ticks from being drawn and reclaims the space for the chart grid/lines." + }, + "gridLineWidth": { + "default": "0.3", + "labels": ["Grid"], + "type": "float", + "description": "Thickness (in pixels) of the gridlines drawn under the chart. The vertical/horizontal gridlines can be turned off entirely by using the drawGrid option. This may be set on a per-axis basis to define each axis' grid separately." + }, + "axisLineWidth": { + "default": "0.3", + "labels": ["Axis display"], + "type": "float", + "description": "Thickness (in pixels) of the x- and y-axis lines." + }, + "axisLineColor": { + "default": "black", + "labels": ["Axis display"], + "type": "string", + "description": "Color of the x- and y-axis lines. Accepts any value which the HTML canvas strokeStyle attribute understands, e.g. 'black' or 'rgb(0, 100, 255)'." + }, + "fillAlpha": { + "default": "0.15", + "labels": ["Error Bars", "Data Series Colors"], + "type": "float (0.0 - 1.0)", + "description": "Error bars (or custom bars) for each series are drawn in the same color as the series, but with partial transparency. This sets the transparency. A value of 0.0 means that the error bars will not be drawn, whereas a value of 1.0 means that the error bars will be as dark as the line for the series itself. This can be used to produce chart lines whose thickness varies at each point." + }, + "axisLabelWidth": { + "default": "50 (y-axis), 60 (x-axis)", + "labels": ["Axis display", "Chart labels"], + "type": "integer", + "description": "Width (in pixels) of the containing divs for x- and y-axis labels. For the y-axis, this also controls the width of the y-axis. Note that for the x-axis, this is independent from pixelsPerLabel, which controls the spacing between labels." + }, + "sigFigs": { + "default": "null", + "labels": ["Value display/formatting"], + "type": "integer", + "description": "By default, dygraphs displays numbers with a fixed number of digits after the decimal point. If you'd prefer to have a fixed number of significant figures, set this option to that number of sig figs. A value of 2, for instance, would cause 1 to be display as 1.0 and 1234 to be displayed as 1.23e+3." + }, + "digitsAfterDecimal": { + "default": "2", + "labels": ["Value display/formatting"], + "type": "integer", + "description": "Unless it's run in scientific mode (see the sigFigs option), dygraphs displays numbers with digitsAfterDecimal digits after the decimal point. Trailing zeros are not displayed, so with a value of 2 you'll get '0', '0.1', '0.12', '123.45' but not '123.456' (it will be rounded to '123.46'). Numbers with absolute value less than 0.1^digitsAfterDecimal (i.e. those which would show up as '0.00') will be displayed in scientific notation." + }, + "maxNumberWidth": { + "default": "6", + "labels": ["Value display/formatting"], + "type": "integer", + "description": "When displaying numbers in normal (not scientific) mode, large numbers will be displayed with many trailing zeros (e.g. 100000000 instead of 1e9). This can lead to unwieldy y-axis labels. If there are more than maxNumberWidth digits to the left of the decimal in a number, dygraphs will switch to scientific notation, even when not operating in scientific mode. If you'd like to see all those digits, set this to something large, like 20 or 30." + }, + "file": { + "default": "(set when constructed)", + "labels": ["Data"], + "type": "string (URL of CSV or CSV), GViz DataTable or 2D Array", + "description": "Sets the data being displayed in the chart. This can only be set when calling updateOptions; it cannot be set from the constructor. For a full description of valid data formats, see the Data Formats page." + }, + "timingName": { + "default": "null", + "labels": ["Debugging", "Deprecated"], + "type": "string", + "description": "Set this option to log timing information. The value of the option will be logged along with the timimg, so that you can distinguish multiple dygraphs on the same page." + }, + "showRangeSelector": { + "default": "false", + "labels": ["Range Selector"], + "type": "boolean", + "description": "Show or hide the range selector widget." + }, + "rangeSelectorHeight": { + "default": "40", + "labels": ["Range Selector"], + "type": "integer", + "description": "Height, in pixels, of the range selector widget. This option can only be specified at Dygraph creation time." + }, + "rangeSelectorPlotStrokeColor": { + "default": "#808FAB", + "labels": ["Range Selector"], + "type": "string", + "description": "The range selector mini plot stroke color. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\". You can also specify null or \"\" to turn off stroke." + }, + "rangeSelectorPlotFillColor": { + "default": "#A7B1C4", + "labels": ["Range Selector"], + "type": "string", + "description": "The range selector mini plot fill color. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\". You can also specify null or \"\" to turn off fill." + }, + "rangeSelectorPlotFillGradientColor": { + "default": "white", + "labels": ["Range Selector"], + "type": "string", + "description": "The top color for the range selector mini plot fill color gradient. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"rgba(255,100,200,42)\" or \"yellow\". You can also specify null or \"\" to disable the gradient and fill with one single color." + }, + "rangeSelectorBackgroundStrokeColor": { + "default": "gray", + "labels": ["Range Selector"], + "type": "string", + "description": "The color of the lines below and on both sides of the range selector mini plot. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\"." + }, + "rangeSelectorBackgroundLineWidth": { + "default": "1", + "labels": ["Range Selector"], + "type": "float", + "description": "The width of the lines below and on both sides of the range selector mini plot." + }, + "rangeSelectorPlotLineWidth": { + "default": "1.5", + "labels": ["Range Selector"], + "type": "float", + "description": "The width of the range selector mini plot line." + }, + "rangeSelectorForegroundStrokeColor": { + "default": "black", + "labels": ["Range Selector"], + "type": "string", + "description": "The color of the lines in the interactive layer of the range selector. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\"." + }, + "rangeSelectorForegroundLineWidth": { + "default": "1", + "labels": ["Range Selector"], + "type": "float", + "description": "The width the lines in the interactive layer of the range selector." + }, + "rangeSelectorAlpha": { + "default": "0.6", + "labels": ["Range Selector"], + "type": "float (0.0 - 1.0)", + "description": "The transparency of the veil that is drawn over the unselected portions of the range selector mini plot. A value of 0 represents full transparency and the unselected portions of the mini plot will appear as normal. A value of 1 represents full opacity and the unselected portions of the mini plot will be hidden." + }, + "showInRangeSelector": { + "default": "null", + "labels": ["Range Selector"], + "type": "boolean", + "description": "Mark this series for inclusion in the range selector. The mini plot curve will be an average of all such series. If this is not specified for any series, the default behavior is to average all the visible series. Setting it for one series will result in that series being charted alone in the range selector. Once it's set for a single series, it needs to be set for all series which should be included (regardless of visibility)." + }, + "animatedZooms": { + "default": "false", + "labels": ["Interactive Elements"], + "type": "boolean", + "description": "Set this option to animate the transition between zoom windows. Applies to programmatic and interactive zooms. Note that if you also set a drawCallback, it will be called several times on each zoom. If you set a zoomCallback, it will only be called after the animation is complete." + }, + "plotter": { + "default": "[DygraphCanvasRenderer.Plotters.fillPlotter, DygraphCanvasRenderer.Plotters.errorPlotter, DygraphCanvasRenderer.Plotters.linePlotter]", + "labels": ["Data Line display"], + "type": "array or function", + "description": "A function (or array of functions) which plot each data series on the chart. TODO(danvk): more details! May be set per-series." + }, + "axes": { + "default": "null", + "labels": ["Configuration"], + "type": "Object", + "description": "Defines per-axis options. Valid keys are 'x', 'y' and 'y2'. Only some options may be set on a per-axis basis. If an option may be set in this way, it will be noted on this page. See also documentation on per-series and per-axis options." + }, + "series": { + "default": "null", + "labels": ["Series"], + "type": "Object", + "description": "Defines per-series options. Its keys match the y-axis label names, and the values are dictionaries themselves that contain options specific to that series." + }, + "plugins": { + "default": "[]", + "labels": ["Configuration"], + "type": "Array", + "description": "Defines per-graph plugins. Useful for per-graph customization" + }, + "dataHandler": { + "default": "(depends on data)", + "labels": ["Data"], + "type": "Dygraph.DataHandler", + "description": "Custom DataHandler. This is an advanced customization. See http://bit.ly/151E7Aq." + } + }; //
+ // NOTE: in addition to parsing as JS, this snippet is expected to be valid + // JSON. This assumption cannot be checked in JS, but it will be checked when + // documentation is generated by the generate-documentation.py script. For the + // most part, this just means that you should always use double quotes. + + // Do a quick sanity check on the options reference. + var warn = function warn(msg) { + if (window.console) window.console.warn(msg); + }; + var flds = ['type', 'default', 'description']; + var valid_cats = ['Annotations', 'Axis display', 'Chart labels', 'CSV parsing', 'Callbacks', 'Data', 'Data Line display', 'Data Series Colors', 'Error Bars', 'Grid', 'Interactive Elements', 'Range Selector', 'Legend', 'Overall display', 'Rolling Averages', 'Series', 'Value display/formatting', 'Zooming', 'Debugging', 'Configuration', 'Deprecated']; + var i; + var cats = {}; + for (i = 0; i < valid_cats.length; i++) cats[valid_cats[i]] = true; + + for (var k in OPTIONS_REFERENCE) { + if (!OPTIONS_REFERENCE.hasOwnProperty(k)) continue; + var op = OPTIONS_REFERENCE[k]; + for (i = 0; i < flds.length; i++) { + if (!op.hasOwnProperty(flds[i])) { + warn('Option ' + k + ' missing "' + flds[i] + '" property'); + } else if (typeof op[flds[i]] != 'string') { + warn(k + '.' + flds[i] + ' must be of type string'); + } + } + var labels = op.labels; + if (typeof labels !== 'object') { + warn('Option "' + k + '" is missing a "labels": [...] option'); + } else { + for (i = 0; i < labels.length; i++) { + if (!cats.hasOwnProperty(labels[i])) { + warn('Option "' + k + '" has label "' + labels[i] + '", which is invalid.'); + } + } + } + } + } +} + +exports['default'] = OPTIONS_REFERENCE; +module.exports = exports['default']; + +}).call(this,require('_process')) + +},{"_process":1}],15:[function(require,module,exports){ +(function (process){ +/** + * @license + * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview DygraphOptions is responsible for parsing and returning + * information about options. + */ + +// TODO: remove this jshint directive & fix the warnings. +/*jshint sub:true */ +"use strict"; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +var _dygraphUtils = require('./dygraph-utils'); + +var utils = _interopRequireWildcard(_dygraphUtils); + +var _dygraphDefaultAttrs = require('./dygraph-default-attrs'); + +var _dygraphDefaultAttrs2 = _interopRequireDefault(_dygraphDefaultAttrs); + +var _dygraphOptionsReference = require('./dygraph-options-reference'); + +var _dygraphOptionsReference2 = _interopRequireDefault(_dygraphOptionsReference); + +/* + * Interesting member variables: (REMOVING THIS LIST AS I CLOSURIZE) + * global_ - global attributes (common among all graphs, AIUI) + * user - attributes set by the user + * series_ - { seriesName -> { idx, yAxis, options }} + */ + +/** + * This parses attributes into an object that can be easily queried. + * + * It doesn't necessarily mean that all options are available, specifically + * if labels are not yet available, since those drive details of the per-series + * and per-axis options. + * + * @param {Dygraph} dygraph The chart to which these options belong. + * @constructor + */ +var DygraphOptions = function DygraphOptions(dygraph) { + /** + * The dygraph. + * @type {!Dygraph} + */ + this.dygraph_ = dygraph; + + /** + * Array of axis index to { series : [ series names ] , options : { axis-specific options. } + * @type {Array.<{series : Array., options : Object}>} @private + */ + this.yAxes_ = []; + + /** + * Contains x-axis specific options, which are stored in the options key. + * This matches the yAxes_ object structure (by being a dictionary with an + * options element) allowing for shared code. + * @type {options: Object} @private + */ + this.xAxis_ = {}; + this.series_ = {}; + + // Once these two objects are initialized, you can call get(); + this.global_ = this.dygraph_.attrs_; + this.user_ = this.dygraph_.user_attrs_ || {}; + + /** + * A list of series in columnar order. + * @type {Array.} + */ + this.labels_ = []; + + this.highlightSeries_ = this.get("highlightSeriesOpts") || {}; + this.reparseSeries(); +}; + +/** + * Not optimal, but does the trick when you're only using two axes. + * If we move to more axes, this can just become a function. + * + * @type {Object.} + * @private + */ +DygraphOptions.AXIS_STRING_MAPPINGS_ = { + 'y': 0, + 'Y': 0, + 'y1': 0, + 'Y1': 0, + 'y2': 1, + 'Y2': 1 +}; + +/** + * @param {string|number} axis + * @private + */ +DygraphOptions.axisToIndex_ = function (axis) { + if (typeof axis == "string") { + if (DygraphOptions.AXIS_STRING_MAPPINGS_.hasOwnProperty(axis)) { + return DygraphOptions.AXIS_STRING_MAPPINGS_[axis]; + } + throw "Unknown axis : " + axis; + } + if (typeof axis == "number") { + if (axis === 0 || axis === 1) { + return axis; + } + throw "Dygraphs only supports two y-axes, indexed from 0-1."; + } + if (axis) { + throw "Unknown axis : " + axis; + } + // No axis specification means axis 0. + return 0; +}; + +/** + * Reparses options that are all related to series. This typically occurs when + * options are either updated, or source data has been made available. + * + * TODO(konigsberg): The method name is kind of weak; fix. + */ +DygraphOptions.prototype.reparseSeries = function () { + var labels = this.get("labels"); + if (!labels) { + return; // -- can't do more for now, will parse after getting the labels. + } + + this.labels_ = labels.slice(1); + + this.yAxes_ = [{ series: [], options: {} }]; // Always one axis at least. + this.xAxis_ = { options: {} }; + this.series_ = {}; + + // Series are specified in the series element: + // + // { + // labels: [ "X", "foo", "bar" ], + // pointSize: 3, + // series : { + // foo : {}, // options for foo + // bar : {} // options for bar + // } + // } + // + // So, if series is found, it's expected to contain per-series data, otherwise set a + // default. + var seriesDict = this.user_.series || {}; + for (var idx = 0; idx < this.labels_.length; idx++) { + var seriesName = this.labels_[idx]; + var optionsForSeries = seriesDict[seriesName] || {}; + var yAxis = DygraphOptions.axisToIndex_(optionsForSeries["axis"]); + + this.series_[seriesName] = { + idx: idx, + yAxis: yAxis, + options: optionsForSeries }; + + if (!this.yAxes_[yAxis]) { + this.yAxes_[yAxis] = { series: [seriesName], options: {} }; + } else { + this.yAxes_[yAxis].series.push(seriesName); + } + } + + var axis_opts = this.user_["axes"] || {}; + utils.update(this.yAxes_[0].options, axis_opts["y"] || {}); + if (this.yAxes_.length > 1) { + utils.update(this.yAxes_[1].options, axis_opts["y2"] || {}); + } + utils.update(this.xAxis_.options, axis_opts["x"] || {}); + + // For "production" code, this gets removed by uglifyjs. + if (typeof process !== 'undefined') { + if ("development" != 'production') { + this.validateOptions_(); + } + } +}; + +/** + * Get a global value. + * + * @param {string} name the name of the option. + */ +DygraphOptions.prototype.get = function (name) { + var result = this.getGlobalUser_(name); + if (result !== null) { + return result; + } + return this.getGlobalDefault_(name); +}; + +DygraphOptions.prototype.getGlobalUser_ = function (name) { + if (this.user_.hasOwnProperty(name)) { + return this.user_[name]; + } + return null; +}; + +DygraphOptions.prototype.getGlobalDefault_ = function (name) { + if (this.global_.hasOwnProperty(name)) { + return this.global_[name]; + } + if (_dygraphDefaultAttrs2['default'].hasOwnProperty(name)) { + return _dygraphDefaultAttrs2['default'][name]; + } + return null; +}; + +/** + * Get a value for a specific axis. If there is no specific value for the axis, + * the global value is returned. + * + * @param {string} name the name of the option. + * @param {string|number} axis the axis to search. Can be the string representation + * ("y", "y2") or the axis number (0, 1). + */ +DygraphOptions.prototype.getForAxis = function (name, axis) { + var axisIdx; + var axisString; + + // Since axis can be a number or a string, straighten everything out here. + if (typeof axis == 'number') { + axisIdx = axis; + axisString = axisIdx === 0 ? "y" : "y2"; + } else { + if (axis == "y1") { + axis = "y"; + } // Standardize on 'y'. Is this bad? I think so. + if (axis == "y") { + axisIdx = 0; + } else if (axis == "y2") { + axisIdx = 1; + } else if (axis == "x") { + axisIdx = -1; // simply a placeholder for below. + } else { + throw "Unknown axis " + axis; + } + axisString = axis; + } + + var userAxis = axisIdx == -1 ? this.xAxis_ : this.yAxes_[axisIdx]; + + // Search the user-specified axis option first. + if (userAxis) { + // This condition could be removed if we always set up this.yAxes_ for y2. + var axisOptions = userAxis.options; + if (axisOptions.hasOwnProperty(name)) { + return axisOptions[name]; + } + } + + // User-specified global options second. + // But, hack, ignore globally-specified 'logscale' for 'x' axis declaration. + if (!(axis === 'x' && name === 'logscale')) { + var result = this.getGlobalUser_(name); + if (result !== null) { + return result; + } + } + // Default axis options third. + var defaultAxisOptions = _dygraphDefaultAttrs2['default'].axes[axisString]; + if (defaultAxisOptions.hasOwnProperty(name)) { + return defaultAxisOptions[name]; + } + + // Default global options last. + return this.getGlobalDefault_(name); +}; + +/** + * Get a value for a specific series. If there is no specific value for the series, + * the value for the axis is returned (and afterwards, the global value.) + * + * @param {string} name the name of the option. + * @param {string} series the series to search. + */ +DygraphOptions.prototype.getForSeries = function (name, series) { + // Honors indexes as series. + if (series === this.dygraph_.getHighlightSeries()) { + if (this.highlightSeries_.hasOwnProperty(name)) { + return this.highlightSeries_[name]; + } + } + + if (!this.series_.hasOwnProperty(series)) { + throw "Unknown series: " + series; + } + + var seriesObj = this.series_[series]; + var seriesOptions = seriesObj["options"]; + if (seriesOptions.hasOwnProperty(name)) { + return seriesOptions[name]; + } + + return this.getForAxis(name, seriesObj["yAxis"]); +}; + +/** + * Returns the number of y-axes on the chart. + * @return {number} the number of axes. + */ +DygraphOptions.prototype.numAxes = function () { + return this.yAxes_.length; +}; + +/** + * Return the y-axis for a given series, specified by name. + */ +DygraphOptions.prototype.axisForSeries = function (series) { + return this.series_[series].yAxis; +}; + +/** + * Returns the options for the specified axis. + */ +// TODO(konigsberg): this is y-axis specific. Support the x axis. +DygraphOptions.prototype.axisOptions = function (yAxis) { + return this.yAxes_[yAxis].options; +}; + +/** + * Return the series associated with an axis. + */ +DygraphOptions.prototype.seriesForAxis = function (yAxis) { + return this.yAxes_[yAxis].series; +}; + +/** + * Return the list of all series, in their columnar order. + */ +DygraphOptions.prototype.seriesNames = function () { + return this.labels_; +}; + +// For "production" code, this gets removed by uglifyjs. +if (typeof process !== 'undefined') { + if ("development" != 'production') { + + /** + * Validate all options. + * This requires OPTIONS_REFERENCE, which is only available in debug builds. + * @private + */ + DygraphOptions.prototype.validateOptions_ = function () { + if (typeof _dygraphOptionsReference2['default'] === 'undefined') { + throw 'Called validateOptions_ in prod build.'; + } + + var that = this; + var validateOption = function validateOption(optionName) { + if (!_dygraphOptionsReference2['default'][optionName]) { + that.warnInvalidOption_(optionName); + } + }; + + var optionsDicts = [this.xAxis_.options, this.yAxes_[0].options, this.yAxes_[1] && this.yAxes_[1].options, this.global_, this.user_, this.highlightSeries_]; + var names = this.seriesNames(); + for (var i = 0; i < names.length; i++) { + var name = names[i]; + if (this.series_.hasOwnProperty(name)) { + optionsDicts.push(this.series_[name].options); + } + } + for (var i = 0; i < optionsDicts.length; i++) { + var dict = optionsDicts[i]; + if (!dict) continue; + for (var optionName in dict) { + if (dict.hasOwnProperty(optionName)) { + validateOption(optionName); + } + } + } + }; + + var WARNINGS = {}; // Only show any particular warning once. + + /** + * Logs a warning about invalid options. + * TODO: make this throw for testing + * @private + */ + DygraphOptions.prototype.warnInvalidOption_ = function (optionName) { + if (!WARNINGS[optionName]) { + WARNINGS[optionName] = true; + var isSeries = this.labels_.indexOf(optionName) >= 0; + if (isSeries) { + console.warn('Use new-style per-series options (saw ' + optionName + ' as top-level options key). See http://bit.ly/1tceaJs'); + } else { + console.warn('Unknown option ' + optionName + ' (full list of options at dygraphs.com/options.html'); + } + throw "invalid option " + optionName; + } + }; + + // Reset list of previously-shown warnings. Used for testing. + DygraphOptions.resetWarnings_ = function () { + WARNINGS = {}; + }; + } +} + +exports['default'] = DygraphOptions; +module.exports = exports['default']; + +}).call(this,require('_process')) + +},{"./dygraph-default-attrs":10,"./dygraph-options-reference":14,"./dygraph-utils":17,"_process":1}],16:[function(require,module,exports){ +/** + * @license + * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview Description of this file. + * @author danvk@google.com (Dan Vanderkam) + * + * A ticker is a function with the following interface: + * + * function(a, b, pixels, options_view, dygraph, forced_values); + * -> [ { v: tick1_v, label: tick1_label[, label_v: label_v1] }, + * { v: tick2_v, label: tick2_label[, label_v: label_v2] }, + * ... + * ] + * + * The returned value is called a "tick list". + * + * Arguments + * --------- + * + * [a, b] is the range of the axis for which ticks are being generated. For a + * numeric axis, these will simply be numbers. For a date axis, these will be + * millis since epoch (convertable to Date objects using "new Date(a)" and "new + * Date(b)"). + * + * opts provides access to chart- and axis-specific options. It can be used to + * access number/date formatting code/options, check for a log scale, etc. + * + * pixels is the length of the axis in pixels. opts('pixelsPerLabel') is the + * minimum amount of space to be allotted to each label. For instance, if + * pixels=400 and opts('pixelsPerLabel')=40 then the ticker should return + * between zero and ten (400/40) ticks. + * + * dygraph is the Dygraph object for which an axis is being constructed. + * + * forced_values is used for secondary y-axes. The tick positions are typically + * set by the primary y-axis, so the secondary y-axis has no choice in where to + * put these. It simply has to generate labels for these data values. + * + * Tick lists + * ---------- + * Typically a tick will have both a grid/tick line and a label at one end of + * that line (at the bottom for an x-axis, at left or right for the y-axis). + * + * A tick may be missing one of these two components: + * - If "label_v" is specified instead of "v", then there will be no tick or + * gridline, just a label. + * - Similarly, if "label" is not specified, then there will be a gridline + * without a label. + * + * This flexibility is useful in a few situations: + * - For log scales, some of the tick lines may be too close to all have labels. + * - For date scales where years are being displayed, it is desirable to display + * tick marks at the beginnings of years but labels (e.g. "2006") in the + * middle of the years. + */ + +/*jshint sub:true */ +/*global Dygraph:false */ +"use strict"; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +var _dygraphUtils = require('./dygraph-utils'); + +var utils = _interopRequireWildcard(_dygraphUtils); + +/** @typedef {Array.<{v:number, label:string, label_v:(string|undefined)}>} */ +var TickList = undefined; // the ' = undefined' keeps jshint happy. + +/** @typedef {function( + * number, + * number, + * number, + * function(string):*, + * Dygraph=, + * Array.= + * ): TickList} + */ +var Ticker = undefined; // the ' = undefined' keeps jshint happy. + +/** @type {Ticker} */ +var numericLinearTicks = function numericLinearTicks(a, b, pixels, opts, dygraph, vals) { + var nonLogscaleOpts = function nonLogscaleOpts(opt) { + if (opt === 'logscale') return false; + return opts(opt); + }; + return numericTicks(a, b, pixels, nonLogscaleOpts, dygraph, vals); +}; + +exports.numericLinearTicks = numericLinearTicks; +/** @type {Ticker} */ +var numericTicks = function numericTicks(a, b, pixels, opts, dygraph, vals) { + var pixels_per_tick = /** @type{number} */opts('pixelsPerLabel'); + var ticks = []; + var i, j, tickV, nTicks; + if (vals) { + for (i = 0; i < vals.length; i++) { + ticks.push({ v: vals[i] }); + } + } else { + // TODO(danvk): factor this log-scale block out into a separate function. + if (opts("logscale")) { + nTicks = Math.floor(pixels / pixels_per_tick); + var minIdx = utils.binarySearch(a, PREFERRED_LOG_TICK_VALUES, 1); + var maxIdx = utils.binarySearch(b, PREFERRED_LOG_TICK_VALUES, -1); + if (minIdx == -1) { + minIdx = 0; + } + if (maxIdx == -1) { + maxIdx = PREFERRED_LOG_TICK_VALUES.length - 1; + } + // Count the number of tick values would appear, if we can get at least + // nTicks / 4 accept them. + var lastDisplayed = null; + if (maxIdx - minIdx >= nTicks / 4) { + for (var idx = maxIdx; idx >= minIdx; idx--) { + var tickValue = PREFERRED_LOG_TICK_VALUES[idx]; + var pixel_coord = Math.log(tickValue / a) / Math.log(b / a) * pixels; + var tick = { v: tickValue }; + if (lastDisplayed === null) { + lastDisplayed = { + tickValue: tickValue, + pixel_coord: pixel_coord + }; + } else { + if (Math.abs(pixel_coord - lastDisplayed.pixel_coord) >= pixels_per_tick) { + lastDisplayed = { + tickValue: tickValue, + pixel_coord: pixel_coord + }; + } else { + tick.label = ""; + } + } + ticks.push(tick); + } + // Since we went in backwards order. + ticks.reverse(); + } + } + + // ticks.length won't be 0 if the log scale function finds values to insert. + if (ticks.length === 0) { + // Basic idea: + // Try labels every 1, 2, 5, 10, 20, 50, 100, etc. + // Calculate the resulting tick spacing (i.e. this.height_ / nTicks). + // The first spacing greater than pixelsPerYLabel is what we use. + // TODO(danvk): version that works on a log scale. + var kmg2 = opts("labelsKMG2"); + var mults, base; + if (kmg2) { + mults = [1, 2, 4, 8, 16, 32, 64, 128, 256]; + base = 16; + } else { + mults = [1, 2, 5, 10, 20, 50, 100]; + base = 10; + } + + // Get the maximum number of permitted ticks based on the + // graph's pixel size and pixels_per_tick setting. + var max_ticks = Math.ceil(pixels / pixels_per_tick); + + // Now calculate the data unit equivalent of this tick spacing. + // Use abs() since graphs may have a reversed Y axis. + var units_per_tick = Math.abs(b - a) / max_ticks; + + // Based on this, get a starting scale which is the largest + // integer power of the chosen base (10 or 16) that still remains + // below the requested pixels_per_tick spacing. + var base_power = Math.floor(Math.log(units_per_tick) / Math.log(base)); + var base_scale = Math.pow(base, base_power); + + // Now try multiples of the starting scale until we find one + // that results in tick marks spaced sufficiently far apart. + // The "mults" array should cover the range 1 .. base^2 to + // adjust for rounding and edge effects. + var scale, low_val, high_val, spacing; + for (j = 0; j < mults.length; j++) { + scale = base_scale * mults[j]; + low_val = Math.floor(a / scale) * scale; + high_val = Math.ceil(b / scale) * scale; + nTicks = Math.abs(high_val - low_val) / scale; + spacing = pixels / nTicks; + if (spacing > pixels_per_tick) break; + } + + // Construct the set of ticks. + // Allow reverse y-axis if it's explicitly requested. + if (low_val > high_val) scale *= -1; + for (i = 0; i <= nTicks; i++) { + tickV = low_val + i * scale; + ticks.push({ v: tickV }); + } + } + } + + var formatter = /**@type{AxisLabelFormatter}*/opts('axisLabelFormatter'); + + // Add labels to the ticks. + for (i = 0; i < ticks.length; i++) { + if (ticks[i].label !== undefined) continue; // Use current label. + // TODO(danvk): set granularity to something appropriate here. + ticks[i].label = formatter.call(dygraph, ticks[i].v, 0, opts, dygraph); + } + + return ticks; +}; + +exports.numericTicks = numericTicks; +/** @type {Ticker} */ +var dateTicker = function dateTicker(a, b, pixels, opts, dygraph, vals) { + var chosen = pickDateTickGranularity(a, b, pixels, opts); + + if (chosen >= 0) { + return getDateAxis(a, b, chosen, opts, dygraph); + } else { + // this can happen if self.width_ is zero. + return []; + } +}; + +exports.dateTicker = dateTicker; +// Time granularity enumeration +var Granularity = { + MILLISECONDLY: 0, + TWO_MILLISECONDLY: 1, + FIVE_MILLISECONDLY: 2, + TEN_MILLISECONDLY: 3, + FIFTY_MILLISECONDLY: 4, + HUNDRED_MILLISECONDLY: 5, + FIVE_HUNDRED_MILLISECONDLY: 6, + SECONDLY: 7, + TWO_SECONDLY: 8, + FIVE_SECONDLY: 9, + TEN_SECONDLY: 10, + THIRTY_SECONDLY: 11, + MINUTELY: 12, + TWO_MINUTELY: 13, + FIVE_MINUTELY: 14, + TEN_MINUTELY: 15, + THIRTY_MINUTELY: 16, + HOURLY: 17, + TWO_HOURLY: 18, + SIX_HOURLY: 19, + DAILY: 20, + TWO_DAILY: 21, + WEEKLY: 22, + MONTHLY: 23, + QUARTERLY: 24, + BIANNUAL: 25, + ANNUAL: 26, + DECADAL: 27, + CENTENNIAL: 28, + NUM_GRANULARITIES: 29 +}; + +exports.Granularity = Granularity; +// Date components enumeration (in the order of the arguments in Date) +// TODO: make this an @enum +var DateField = { + DATEFIELD_Y: 0, + DATEFIELD_M: 1, + DATEFIELD_D: 2, + DATEFIELD_HH: 3, + DATEFIELD_MM: 4, + DATEFIELD_SS: 5, + DATEFIELD_MS: 6, + NUM_DATEFIELDS: 7 +}; + +/** + * The value of datefield will start at an even multiple of "step", i.e. + * if datefield=SS and step=5 then the first tick will be on a multiple of 5s. + * + * For granularities <= HOURLY, ticks are generated every `spacing` ms. + * + * At coarser granularities, ticks are generated by incrementing `datefield` by + * `step`. In this case, the `spacing` value is only used to estimate the + * number of ticks. It should roughly correspond to the spacing between + * adjacent ticks. + * + * @type {Array.<{datefield:number, step:number, spacing:number}>} + */ +var TICK_PLACEMENT = []; +TICK_PLACEMENT[Granularity.MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 1, spacing: 1 }; +TICK_PLACEMENT[Granularity.TWO_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 2, spacing: 2 }; +TICK_PLACEMENT[Granularity.FIVE_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 5, spacing: 5 }; +TICK_PLACEMENT[Granularity.TEN_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 10, spacing: 10 }; +TICK_PLACEMENT[Granularity.FIFTY_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 50, spacing: 50 }; +TICK_PLACEMENT[Granularity.HUNDRED_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 100, spacing: 100 }; +TICK_PLACEMENT[Granularity.FIVE_HUNDRED_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 500, spacing: 500 }; +TICK_PLACEMENT[Granularity.SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 1, spacing: 1000 * 1 }; +TICK_PLACEMENT[Granularity.TWO_SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 2, spacing: 1000 * 2 }; +TICK_PLACEMENT[Granularity.FIVE_SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 5, spacing: 1000 * 5 }; +TICK_PLACEMENT[Granularity.TEN_SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 10, spacing: 1000 * 10 }; +TICK_PLACEMENT[Granularity.THIRTY_SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 30, spacing: 1000 * 30 }; +TICK_PLACEMENT[Granularity.MINUTELY] = { datefield: DateField.DATEFIELD_MM, step: 1, spacing: 1000 * 60 }; +TICK_PLACEMENT[Granularity.TWO_MINUTELY] = { datefield: DateField.DATEFIELD_MM, step: 2, spacing: 1000 * 60 * 2 }; +TICK_PLACEMENT[Granularity.FIVE_MINUTELY] = { datefield: DateField.DATEFIELD_MM, step: 5, spacing: 1000 * 60 * 5 }; +TICK_PLACEMENT[Granularity.TEN_MINUTELY] = { datefield: DateField.DATEFIELD_MM, step: 10, spacing: 1000 * 60 * 10 }; +TICK_PLACEMENT[Granularity.THIRTY_MINUTELY] = { datefield: DateField.DATEFIELD_MM, step: 30, spacing: 1000 * 60 * 30 }; +TICK_PLACEMENT[Granularity.HOURLY] = { datefield: DateField.DATEFIELD_HH, step: 1, spacing: 1000 * 3600 }; +TICK_PLACEMENT[Granularity.TWO_HOURLY] = { datefield: DateField.DATEFIELD_HH, step: 2, spacing: 1000 * 3600 * 2 }; +TICK_PLACEMENT[Granularity.SIX_HOURLY] = { datefield: DateField.DATEFIELD_HH, step: 6, spacing: 1000 * 3600 * 6 }; +TICK_PLACEMENT[Granularity.DAILY] = { datefield: DateField.DATEFIELD_D, step: 1, spacing: 1000 * 86400 }; +TICK_PLACEMENT[Granularity.TWO_DAILY] = { datefield: DateField.DATEFIELD_D, step: 2, spacing: 1000 * 86400 * 2 }; +TICK_PLACEMENT[Granularity.WEEKLY] = { datefield: DateField.DATEFIELD_D, step: 7, spacing: 1000 * 604800 }; +TICK_PLACEMENT[Granularity.MONTHLY] = { datefield: DateField.DATEFIELD_M, step: 1, spacing: 1000 * 7200 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 / 12 +TICK_PLACEMENT[Granularity.QUARTERLY] = { datefield: DateField.DATEFIELD_M, step: 3, spacing: 1000 * 21600 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 / 4 +TICK_PLACEMENT[Granularity.BIANNUAL] = { datefield: DateField.DATEFIELD_M, step: 6, spacing: 1000 * 43200 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 / 2 +TICK_PLACEMENT[Granularity.ANNUAL] = { datefield: DateField.DATEFIELD_Y, step: 1, spacing: 1000 * 86400 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 * 1 +TICK_PLACEMENT[Granularity.DECADAL] = { datefield: DateField.DATEFIELD_Y, step: 10, spacing: 1000 * 864000 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 * 10 +TICK_PLACEMENT[Granularity.CENTENNIAL] = { datefield: DateField.DATEFIELD_Y, step: 100, spacing: 1000 * 8640000 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 * 100 + +/** + * This is a list of human-friendly values at which to show tick marks on a log + * scale. It is k * 10^n, where k=1..9 and n=-39..+39, so: + * ..., 1, 2, 3, 4, 5, ..., 9, 10, 20, 30, ..., 90, 100, 200, 300, ... + * NOTE: this assumes that utils.LOG_SCALE = 10. + * @type {Array.} + */ +var PREFERRED_LOG_TICK_VALUES = (function () { + var vals = []; + for (var power = -39; power <= 39; power++) { + var range = Math.pow(10, power); + for (var mult = 1; mult <= 9; mult++) { + var val = range * mult; + vals.push(val); + } + } + return vals; +})(); + +/** + * Determine the correct granularity of ticks on a date axis. + * + * @param {number} a Left edge of the chart (ms) + * @param {number} b Right edge of the chart (ms) + * @param {number} pixels Size of the chart in the relevant dimension (width). + * @param {function(string):*} opts Function mapping from option name -> value. + * @return {number} The appropriate axis granularity for this chart. See the + * enumeration of possible values in dygraph-tickers.js. + */ +var pickDateTickGranularity = function pickDateTickGranularity(a, b, pixels, opts) { + var pixels_per_tick = /** @type{number} */opts('pixelsPerLabel'); + for (var i = 0; i < Granularity.NUM_GRANULARITIES; i++) { + var num_ticks = numDateTicks(a, b, i); + if (pixels / num_ticks >= pixels_per_tick) { + return i; + } + } + return -1; +}; + +/** + * Compute the number of ticks on a date axis for a given granularity. + * @param {number} start_time + * @param {number} end_time + * @param {number} granularity (one of the granularities enumerated above) + * @return {number} (Approximate) number of ticks that would result. + */ +var numDateTicks = function numDateTicks(start_time, end_time, granularity) { + var spacing = TICK_PLACEMENT[granularity].spacing; + return Math.round(1.0 * (end_time - start_time) / spacing); +}; + +/** + * Compute the positions and labels of ticks on a date axis for a given granularity. + * @param {number} start_time + * @param {number} end_time + * @param {number} granularity (one of the granularities enumerated above) + * @param {function(string):*} opts Function mapping from option name -> value. + * @param {Dygraph=} dg + * @return {!TickList} + */ +var getDateAxis = function getDateAxis(start_time, end_time, granularity, opts, dg) { + var formatter = /** @type{AxisLabelFormatter} */opts("axisLabelFormatter"); + var utc = opts("labelsUTC"); + var accessors = utc ? utils.DateAccessorsUTC : utils.DateAccessorsLocal; + + var datefield = TICK_PLACEMENT[granularity].datefield; + var step = TICK_PLACEMENT[granularity].step; + var spacing = TICK_PLACEMENT[granularity].spacing; + + // Choose a nice tick position before the initial instant. + // Currently, this code deals properly with the existent daily granularities: + // DAILY (with step of 1) and WEEKLY (with step of 7 but specially handled). + // Other daily granularities (say TWO_DAILY) should also be handled specially + // by setting the start_date_offset to 0. + var start_date = new Date(start_time); + var date_array = []; + date_array[DateField.DATEFIELD_Y] = accessors.getFullYear(start_date); + date_array[DateField.DATEFIELD_M] = accessors.getMonth(start_date); + date_array[DateField.DATEFIELD_D] = accessors.getDate(start_date); + date_array[DateField.DATEFIELD_HH] = accessors.getHours(start_date); + date_array[DateField.DATEFIELD_MM] = accessors.getMinutes(start_date); + date_array[DateField.DATEFIELD_SS] = accessors.getSeconds(start_date); + date_array[DateField.DATEFIELD_MS] = accessors.getMilliseconds(start_date); + + var start_date_offset = date_array[datefield] % step; + if (granularity == Granularity.WEEKLY) { + // This will put the ticks on Sundays. + start_date_offset = accessors.getDay(start_date); + } + + date_array[datefield] -= start_date_offset; + for (var df = datefield + 1; df < DateField.NUM_DATEFIELDS; df++) { + // The minimum value is 1 for the day of month, and 0 for all other fields. + date_array[df] = df === DateField.DATEFIELD_D ? 1 : 0; + } + + // Generate the ticks. + // For granularities not coarser than HOURLY we use the fact that: + // the number of milliseconds between ticks is constant + // and equal to the defined spacing. + // Otherwise we rely on the 'roll over' property of the Date functions: + // when some date field is set to a value outside of its logical range, + // the excess 'rolls over' the next (more significant) field. + // However, when using local time with DST transitions, + // there are dates that do not represent any time value at all + // (those in the hour skipped at the 'spring forward'), + // and the JavaScript engines usually return an equivalent value. + // Hence we have to check that the date is properly increased at each step, + // returning a date at a nice tick position. + var ticks = []; + var tick_date = accessors.makeDate.apply(null, date_array); + var tick_time = tick_date.getTime(); + if (granularity <= Granularity.HOURLY) { + if (tick_time < start_time) { + tick_time += spacing; + tick_date = new Date(tick_time); + } + while (tick_time <= end_time) { + ticks.push({ v: tick_time, + label: formatter.call(dg, tick_date, granularity, opts, dg) + }); + tick_time += spacing; + tick_date = new Date(tick_time); + } + } else { + if (tick_time < start_time) { + date_array[datefield] += step; + tick_date = accessors.makeDate.apply(null, date_array); + tick_time = tick_date.getTime(); + } + while (tick_time <= end_time) { + if (granularity >= Granularity.DAILY || accessors.getHours(tick_date) % step === 0) { + ticks.push({ v: tick_time, + label: formatter.call(dg, tick_date, granularity, opts, dg) + }); + } + date_array[datefield] += step; + tick_date = accessors.makeDate.apply(null, date_array); + tick_time = tick_date.getTime(); + } + } + return ticks; +}; +exports.getDateAxis = getDateAxis; + +},{"./dygraph-utils":17}],17:[function(require,module,exports){ +/** + * @license + * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview This file contains utility functions used by dygraphs. These + * are typically static (i.e. not related to any particular dygraph). Examples + * include date/time formatting functions, basic algorithms (e.g. binary + * search) and generic DOM-manipulation functions. + */ + +/*global Dygraph:false, Node:false */ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.removeEvent = removeEvent; +exports.cancelEvent = cancelEvent; +exports.hsvToRGB = hsvToRGB; +exports.findPos = findPos; +exports.pageX = pageX; +exports.pageY = pageY; +exports.dragGetX_ = dragGetX_; +exports.dragGetY_ = dragGetY_; +exports.isOK = isOK; +exports.isValidPoint = isValidPoint; +exports.floatFormat = floatFormat; +exports.zeropad = zeropad; +exports.hmsString_ = hmsString_; +exports.dateString_ = dateString_; +exports.round_ = round_; +exports.binarySearch = binarySearch; +exports.dateParser = dateParser; +exports.dateStrToMillis = dateStrToMillis; +exports.update = update; +exports.updateDeep = updateDeep; +exports.isArrayLike = isArrayLike; +exports.isDateLike = isDateLike; +exports.clone = clone; +exports.createCanvas = createCanvas; +exports.getContextPixelRatio = getContextPixelRatio; +exports.Iterator = Iterator; +exports.createIterator = createIterator; +exports.repeatAndCleanup = repeatAndCleanup; +exports.isPixelChangingOptionList = isPixelChangingOptionList; +exports.detectLineDelimiter = detectLineDelimiter; +exports.isNodeContainedBy = isNodeContainedBy; +exports.pow = pow; +exports.toRGB_ = toRGB_; +exports.isCanvasSupported = isCanvasSupported; +exports.parseFloat_ = parseFloat_; +exports.numberValueFormatter = numberValueFormatter; +exports.numberAxisLabelFormatter = numberAxisLabelFormatter; +exports.dateAxisLabelFormatter = dateAxisLabelFormatter; +exports.dateValueFormatter = dateValueFormatter; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj["default"] = obj; return newObj; } } + +var _dygraphTickers = require('./dygraph-tickers'); + +var DygraphTickers = _interopRequireWildcard(_dygraphTickers); + +var LOG_SCALE = 10; +exports.LOG_SCALE = LOG_SCALE; +var LN_TEN = Math.log(LOG_SCALE); + +exports.LN_TEN = LN_TEN; +/** + * @private + * @param {number} x + * @return {number} + */ +var log10 = function log10(x) { + return Math.log(x) / LN_TEN; +}; + +exports.log10 = log10; +/** + * @private + * @param {number} r0 + * @param {number} r1 + * @param {number} pct + * @return {number} + */ +var logRangeFraction = function logRangeFraction(r0, r1, pct) { + // Computing the inverse of toPercentXCoord. The function was arrived at with + // the following steps: + // + // Original calcuation: + // pct = (log(x) - log(xRange[0])) / (log(xRange[1]) - log(xRange[0]))); + // + // Multiply both sides by the right-side denominator. + // pct * (log(xRange[1] - log(xRange[0]))) = log(x) - log(xRange[0]) + // + // add log(xRange[0]) to both sides + // log(xRange[0]) + (pct * (log(xRange[1]) - log(xRange[0])) = log(x); + // + // Swap both sides of the equation, + // log(x) = log(xRange[0]) + (pct * (log(xRange[1]) - log(xRange[0])) + // + // Use both sides as the exponent in 10^exp and we're done. + // x = 10 ^ (log(xRange[0]) + (pct * (log(xRange[1]) - log(xRange[0]))) + + var logr0 = log10(r0); + var logr1 = log10(r1); + var exponent = logr0 + pct * (logr1 - logr0); + var value = Math.pow(LOG_SCALE, exponent); + return value; +}; + +exports.logRangeFraction = logRangeFraction; +/** A dotted line stroke pattern. */ +var DOTTED_LINE = [2, 2]; +exports.DOTTED_LINE = DOTTED_LINE; +/** A dashed line stroke pattern. */ +var DASHED_LINE = [7, 3]; +exports.DASHED_LINE = DASHED_LINE; +/** A dot dash stroke pattern. */ +var DOT_DASH_LINE = [7, 2, 2, 2]; + +exports.DOT_DASH_LINE = DOT_DASH_LINE; +// Directions for panning and zooming. Use bit operations when combined +// values are possible. +var HORIZONTAL = 1; +exports.HORIZONTAL = HORIZONTAL; +var VERTICAL = 2; + +exports.VERTICAL = VERTICAL; +/** + * Return the 2d context for a dygraph canvas. + * + * This method is only exposed for the sake of replacing the function in + * automated tests. + * + * @param {!HTMLCanvasElement} canvas + * @return {!CanvasRenderingContext2D} + * @private + */ +var getContext = function getContext(canvas) { + return (/** @type{!CanvasRenderingContext2D}*/canvas.getContext("2d") + ); +}; + +exports.getContext = getContext; +/** + * Add an event handler. + * @param {!Node} elem The element to add the event to. + * @param {string} type The type of the event, e.g. 'click' or 'mousemove'. + * @param {function(Event):(boolean|undefined)} fn The function to call + * on the event. The function takes one parameter: the event object. + * @private + */ +var addEvent = function addEvent(elem, type, fn) { + elem.addEventListener(type, fn, false); +}; + +exports.addEvent = addEvent; +/** + * Remove an event handler. + * @param {!Node} elem The element to remove the event from. + * @param {string} type The type of the event, e.g. 'click' or 'mousemove'. + * @param {function(Event):(boolean|undefined)} fn The function to call + * on the event. The function takes one parameter: the event object. + */ + +function removeEvent(elem, type, fn) { + elem.removeEventListener(type, fn, false); +} + +; + +/** + * Cancels further processing of an event. This is useful to prevent default + * browser actions, e.g. highlighting text on a double-click. + * Based on the article at + * http://www.switchonthecode.com/tutorials/javascript-tutorial-the-scroll-wheel + * @param {!Event} e The event whose normal behavior should be canceled. + * @private + */ + +function cancelEvent(e) { + e = e ? e : window.event; + if (e.stopPropagation) { + e.stopPropagation(); + } + if (e.preventDefault) { + e.preventDefault(); + } + e.cancelBubble = true; + e.cancel = true; + e.returnValue = false; + return false; +} + +; + +/** + * Convert hsv values to an rgb(r,g,b) string. Taken from MochiKit.Color. This + * is used to generate default series colors which are evenly spaced on the + * color wheel. + * @param { number } hue Range is 0.0-1.0. + * @param { number } saturation Range is 0.0-1.0. + * @param { number } value Range is 0.0-1.0. + * @return { string } "rgb(r,g,b)" where r, g and b range from 0-255. + * @private + */ + +function hsvToRGB(hue, saturation, value) { + var red; + var green; + var blue; + if (saturation === 0) { + red = value; + green = value; + blue = value; + } else { + var i = Math.floor(hue * 6); + var f = hue * 6 - i; + var p = value * (1 - saturation); + var q = value * (1 - saturation * f); + var t = value * (1 - saturation * (1 - f)); + switch (i) { + case 1: + red = q;green = value;blue = p;break; + case 2: + red = p;green = value;blue = t;break; + case 3: + red = p;green = q;blue = value;break; + case 4: + red = t;green = p;blue = value;break; + case 5: + red = value;green = p;blue = q;break; + case 6: // fall through + case 0: + red = value;green = t;blue = p;break; + } + } + red = Math.floor(255 * red + 0.5); + green = Math.floor(255 * green + 0.5); + blue = Math.floor(255 * blue + 0.5); + return 'rgb(' + red + ',' + green + ',' + blue + ')'; +} + +; + +/** + * Find the coordinates of an object relative to the top left of the page. + * + * @param {Node} obj + * @return {{x:number,y:number}} + * @private + */ + +function findPos(obj) { + var p = obj.getBoundingClientRect(), + w = window, + d = document.documentElement; + + return { + x: p.left + (w.pageXOffset || d.scrollLeft), + y: p.top + (w.pageYOffset || d.scrollTop) + }; +} + +; + +/** + * Returns the x-coordinate of the event in a coordinate system where the + * top-left corner of the page (not the window) is (0,0). + * Taken from MochiKit.Signal + * @param {!Event} e + * @return {number} + * @private + */ + +function pageX(e) { + return !e.pageX || e.pageX < 0 ? 0 : e.pageX; +} + +; + +/** + * Returns the y-coordinate of the event in a coordinate system where the + * top-left corner of the page (not the window) is (0,0). + * Taken from MochiKit.Signal + * @param {!Event} e + * @return {number} + * @private + */ + +function pageY(e) { + return !e.pageY || e.pageY < 0 ? 0 : e.pageY; +} + +; + +/** + * Converts page the x-coordinate of the event to pixel x-coordinates on the + * canvas (i.e. DOM Coords). + * @param {!Event} e Drag event. + * @param {!DygraphInteractionContext} context Interaction context object. + * @return {number} The amount by which the drag has moved to the right. + */ + +function dragGetX_(e, context) { + return pageX(e) - context.px; +} + +; + +/** + * Converts page the y-coordinate of the event to pixel y-coordinates on the + * canvas (i.e. DOM Coords). + * @param {!Event} e Drag event. + * @param {!DygraphInteractionContext} context Interaction context object. + * @return {number} The amount by which the drag has moved down. + */ + +function dragGetY_(e, context) { + return pageY(e) - context.py; +} + +; + +/** + * This returns true unless the parameter is 0, null, undefined or NaN. + * TODO(danvk): rename this function to something like 'isNonZeroNan'. + * + * @param {number} x The number to consider. + * @return {boolean} Whether the number is zero or NaN. + * @private + */ + +function isOK(x) { + return !!x && !isNaN(x); +} + +; + +/** + * @param {{x:?number,y:?number,yval:?number}} p The point to consider, valid + * points are {x, y} objects + * @param {boolean=} opt_allowNaNY Treat point with y=NaN as valid + * @return {boolean} Whether the point has numeric x and y. + * @private + */ + +function isValidPoint(p, opt_allowNaNY) { + if (!p) return false; // null or undefined object + if (p.yval === null) return false; // missing point + if (p.x === null || p.x === undefined) return false; + if (p.y === null || p.y === undefined) return false; + if (isNaN(p.x) || !opt_allowNaNY && isNaN(p.y)) return false; + return true; +} + +; + +/** + * Number formatting function which mimics the behavior of %g in printf, i.e. + * either exponential or fixed format (without trailing 0s) is used depending on + * the length of the generated string. The advantage of this format is that + * there is a predictable upper bound on the resulting string length, + * significant figures are not dropped, and normal numbers are not displayed in + * exponential notation. + * + * NOTE: JavaScript's native toPrecision() is NOT a drop-in replacement for %g. + * It creates strings which are too long for absolute values between 10^-4 and + * 10^-6, e.g. '0.00001' instead of '1e-5'. See tests/number-format.html for + * output examples. + * + * @param {number} x The number to format + * @param {number=} opt_precision The precision to use, default 2. + * @return {string} A string formatted like %g in printf. The max generated + * string length should be precision + 6 (e.g 1.123e+300). + */ + +function floatFormat(x, opt_precision) { + // Avoid invalid precision values; [1, 21] is the valid range. + var p = Math.min(Math.max(1, opt_precision || 2), 21); + + // This is deceptively simple. The actual algorithm comes from: + // + // Max allowed length = p + 4 + // where 4 comes from 'e+n' and '.'. + // + // Length of fixed format = 2 + y + p + // where 2 comes from '0.' and y = # of leading zeroes. + // + // Equating the two and solving for y yields y = 2, or 0.00xxxx which is + // 1.0e-3. + // + // Since the behavior of toPrecision() is identical for larger numbers, we + // don't have to worry about the other bound. + // + // Finally, the argument for toExponential() is the number of trailing digits, + // so we take off 1 for the value before the '.'. + return Math.abs(x) < 1.0e-3 && x !== 0.0 ? x.toExponential(p - 1) : x.toPrecision(p); +} + +; + +/** + * Converts '9' to '09' (useful for dates) + * @param {number} x + * @return {string} + * @private + */ + +function zeropad(x) { + if (x < 10) return "0" + x;else return "" + x; +} + +; + +/** + * Date accessors to get the parts of a calendar date (year, month, + * day, hour, minute, second and millisecond) according to local time, + * and factory method to call the Date constructor with an array of arguments. + */ +var DateAccessorsLocal = { + getFullYear: function getFullYear(d) { + return d.getFullYear(); + }, + getMonth: function getMonth(d) { + return d.getMonth(); + }, + getDate: function getDate(d) { + return d.getDate(); + }, + getHours: function getHours(d) { + return d.getHours(); + }, + getMinutes: function getMinutes(d) { + return d.getMinutes(); + }, + getSeconds: function getSeconds(d) { + return d.getSeconds(); + }, + getMilliseconds: function getMilliseconds(d) { + return d.getMilliseconds(); + }, + getDay: function getDay(d) { + return d.getDay(); + }, + makeDate: function makeDate(y, m, d, hh, mm, ss, ms) { + return new Date(y, m, d, hh, mm, ss, ms); + } +}; + +exports.DateAccessorsLocal = DateAccessorsLocal; +/** + * Date accessors to get the parts of a calendar date (year, month, + * day of month, hour, minute, second and millisecond) according to UTC time, + * and factory method to call the Date constructor with an array of arguments. + */ +var DateAccessorsUTC = { + getFullYear: function getFullYear(d) { + return d.getUTCFullYear(); + }, + getMonth: function getMonth(d) { + return d.getUTCMonth(); + }, + getDate: function getDate(d) { + return d.getUTCDate(); + }, + getHours: function getHours(d) { + return d.getUTCHours(); + }, + getMinutes: function getMinutes(d) { + return d.getUTCMinutes(); + }, + getSeconds: function getSeconds(d) { + return d.getUTCSeconds(); + }, + getMilliseconds: function getMilliseconds(d) { + return d.getUTCMilliseconds(); + }, + getDay: function getDay(d) { + return d.getUTCDay(); + }, + makeDate: function makeDate(y, m, d, hh, mm, ss, ms) { + return new Date(Date.UTC(y, m, d, hh, mm, ss, ms)); + } +}; + +exports.DateAccessorsUTC = DateAccessorsUTC; +/** + * Return a string version of the hours, minutes and seconds portion of a date. + * @param {number} hh The hours (from 0-23) + * @param {number} mm The minutes (from 0-59) + * @param {number} ss The seconds (from 0-59) + * @return {string} A time of the form "HH:MM" or "HH:MM:SS" + * @private + */ + +function hmsString_(hh, mm, ss, ms) { + var ret = zeropad(hh) + ":" + zeropad(mm); + if (ss) { + ret += ":" + zeropad(ss); + if (ms) { + var str = "" + ms; + ret += "." + ('000' + str).substring(str.length); + } + } + return ret; +} + +; + +/** + * Convert a JS date (millis since epoch) to a formatted string. + * @param {number} time The JavaScript time value (ms since epoch) + * @param {boolean} utc Whether output UTC or local time + * @return {string} A date of one of these forms: + * "YYYY/MM/DD", "YYYY/MM/DD HH:MM" or "YYYY/MM/DD HH:MM:SS" + * @private + */ + +function dateString_(time, utc) { + var accessors = utc ? DateAccessorsUTC : DateAccessorsLocal; + var date = new Date(time); + var y = accessors.getFullYear(date); + var m = accessors.getMonth(date); + var d = accessors.getDate(date); + var hh = accessors.getHours(date); + var mm = accessors.getMinutes(date); + var ss = accessors.getSeconds(date); + var ms = accessors.getMilliseconds(date); + // Get a year string: + var year = "" + y; + // Get a 0 padded month string + var month = zeropad(m + 1); //months are 0-offset, sigh + // Get a 0 padded day string + var day = zeropad(d); + var frac = hh * 3600 + mm * 60 + ss + 1e-3 * ms; + var ret = year + "/" + month + "/" + day; + if (frac) { + ret += " " + hmsString_(hh, mm, ss, ms); + } + return ret; +} + +; + +/** + * Round a number to the specified number of digits past the decimal point. + * @param {number} num The number to round + * @param {number} places The number of decimals to which to round + * @return {number} The rounded number + * @private + */ + +function round_(num, places) { + var shift = Math.pow(10, places); + return Math.round(num * shift) / shift; +} + +; + +/** + * Implementation of binary search over an array. + * Currently does not work when val is outside the range of arry's values. + * @param {number} val the value to search for + * @param {Array.} arry is the value over which to search + * @param {number} abs If abs > 0, find the lowest entry greater than val + * If abs < 0, find the highest entry less than val. + * If abs == 0, find the entry that equals val. + * @param {number=} low The first index in arry to consider (optional) + * @param {number=} high The last index in arry to consider (optional) + * @return {number} Index of the element, or -1 if it isn't found. + * @private + */ + +function binarySearch(_x, _x2, _x3, _x4, _x5) { + var _again = true; + + _function: while (_again) { + var val = _x, + arry = _x2, + abs = _x3, + low = _x4, + high = _x5; + _again = false; + + if (low === null || low === undefined || high === null || high === undefined) { + low = 0; + high = arry.length - 1; + } + if (low > high) { + return -1; + } + if (abs === null || abs === undefined) { + abs = 0; + } + var validIndex = function validIndex(idx) { + return idx >= 0 && idx < arry.length; + }; + var mid = parseInt((low + high) / 2, 10); + var element = arry[mid]; + var idx; + if (element == val) { + return mid; + } else if (element > val) { + if (abs > 0) { + // Accept if element > val, but also if prior element < val. + idx = mid - 1; + if (validIndex(idx) && arry[idx] < val) { + return mid; + } + } + _x = val; + _x2 = arry; + _x3 = abs; + _x4 = low; + _x5 = mid - 1; + _again = true; + validIndex = mid = element = idx = undefined; + continue _function; + } else if (element < val) { + if (abs < 0) { + // Accept if element < val, but also if prior element > val. + idx = mid + 1; + if (validIndex(idx) && arry[idx] > val) { + return mid; + } + } + _x = val; + _x2 = arry; + _x3 = abs; + _x4 = mid + 1; + _x5 = high; + _again = true; + validIndex = mid = element = idx = undefined; + continue _function; + } + return -1; // can't actually happen, but makes closure compiler happy + } +} + +; + +/** + * Parses a date, returning the number of milliseconds since epoch. This can be + * passed in as an xValueParser in the Dygraph constructor. + * TODO(danvk): enumerate formats that this understands. + * + * @param {string} dateStr A date in a variety of possible string formats. + * @return {number} Milliseconds since epoch. + * @private + */ + +function dateParser(dateStr) { + var dateStrSlashed; + var d; + + // Let the system try the format first, with one caveat: + // YYYY-MM-DD[ HH:MM:SS] is interpreted as UTC by a variety of browsers. + // dygraphs displays dates in local time, so this will result in surprising + // inconsistencies. But if you specify "T" or "Z" (i.e. YYYY-MM-DDTHH:MM:SS), + // then you probably know what you're doing, so we'll let you go ahead. + // Issue: http://code.google.com/p/dygraphs/issues/detail?id=255 + if (dateStr.search("-") == -1 || dateStr.search("T") != -1 || dateStr.search("Z") != -1) { + d = dateStrToMillis(dateStr); + if (d && !isNaN(d)) return d; + } + + if (dateStr.search("-") != -1) { + // e.g. '2009-7-12' or '2009-07-12' + dateStrSlashed = dateStr.replace("-", "/", "g"); + while (dateStrSlashed.search("-") != -1) { + dateStrSlashed = dateStrSlashed.replace("-", "/"); + } + d = dateStrToMillis(dateStrSlashed); + } else if (dateStr.length == 8) { + // e.g. '20090712' + // TODO(danvk): remove support for this format. It's confusing. + dateStrSlashed = dateStr.substr(0, 4) + "/" + dateStr.substr(4, 2) + "/" + dateStr.substr(6, 2); + d = dateStrToMillis(dateStrSlashed); + } else { + // Any format that Date.parse will accept, e.g. "2009/07/12" or + // "2009/07/12 12:34:56" + d = dateStrToMillis(dateStr); + } + + if (!d || isNaN(d)) { + console.error("Couldn't parse " + dateStr + " as a date"); + } + return d; +} + +; + +/** + * This is identical to JavaScript's built-in Date.parse() method, except that + * it doesn't get replaced with an incompatible method by aggressive JS + * libraries like MooTools or Joomla. + * @param {string} str The date string, e.g. "2011/05/06" + * @return {number} millis since epoch + * @private + */ + +function dateStrToMillis(str) { + return new Date(str).getTime(); +} + +; + +// These functions are all based on MochiKit. +/** + * Copies all the properties from o to self. + * + * @param {!Object} self + * @param {!Object} o + * @return {!Object} + */ + +function update(self, o) { + if (typeof o != 'undefined' && o !== null) { + for (var k in o) { + if (o.hasOwnProperty(k)) { + self[k] = o[k]; + } + } + } + return self; +} + +; + +/** + * Copies all the properties from o to self. + * + * @param {!Object} self + * @param {!Object} o + * @return {!Object} + * @private + */ + +function updateDeep(self, o) { + // Taken from http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object + function isNode(o) { + return typeof Node === "object" ? o instanceof Node : typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName === "string"; + } + + if (typeof o != 'undefined' && o !== null) { + for (var k in o) { + if (o.hasOwnProperty(k)) { + if (o[k] === null) { + self[k] = null; + } else if (isArrayLike(o[k])) { + self[k] = o[k].slice(); + } else if (isNode(o[k])) { + // DOM objects are shallowly-copied. + self[k] = o[k]; + } else if (typeof o[k] == 'object') { + if (typeof self[k] != 'object' || self[k] === null) { + self[k] = {}; + } + updateDeep(self[k], o[k]); + } else { + self[k] = o[k]; + } + } + } + } + return self; +} + +; + +/** + * @param {*} o + * @return {boolean} + * @private + */ + +function isArrayLike(o) { + var typ = typeof o; + if (typ != 'object' && !(typ == 'function' && typeof o.item == 'function') || o === null || typeof o.length != 'number' || o.nodeType === 3) { + return false; + } + return true; +} + +; + +/** + * @param {Object} o + * @return {boolean} + * @private + */ + +function isDateLike(o) { + if (typeof o != "object" || o === null || typeof o.getTime != 'function') { + return false; + } + return true; +} + +; + +/** + * Note: this only seems to work for arrays. + * @param {!Array} o + * @return {!Array} + * @private + */ + +function clone(o) { + // TODO(danvk): figure out how MochiKit's version works + var r = []; + for (var i = 0; i < o.length; i++) { + if (isArrayLike(o[i])) { + r.push(clone(o[i])); + } else { + r.push(o[i]); + } + } + return r; +} + +; + +/** + * Create a new canvas element. + * + * @return {!HTMLCanvasElement} + * @private + */ + +function createCanvas() { + return document.createElement('canvas'); +} + +; + +/** + * Returns the context's pixel ratio, which is the ratio between the device + * pixel ratio and the backing store ratio. Typically this is 1 for conventional + * displays, and > 1 for HiDPI displays (such as the Retina MBP). + * See http://www.html5rocks.com/en/tutorials/canvas/hidpi/ for more details. + * + * @param {!CanvasRenderingContext2D} context The canvas's 2d context. + * @return {number} The ratio of the device pixel ratio and the backing store + * ratio for the specified context. + */ + +function getContextPixelRatio(context) { + try { + var devicePixelRatio = window.devicePixelRatio; + var backingStoreRatio = context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1; + if (devicePixelRatio !== undefined) { + return devicePixelRatio / backingStoreRatio; + } else { + // At least devicePixelRatio must be defined for this ratio to make sense. + // We default backingStoreRatio to 1: this does not exist on some browsers + // (i.e. desktop Chrome). + return 1; + } + } catch (e) { + return 1; + } +} + +; + +/** + * TODO(danvk): use @template here when it's better supported for classes. + * @param {!Array} array + * @param {number} start + * @param {number} length + * @param {function(!Array,?):boolean=} predicate + * @constructor + */ + +function Iterator(array, start, length, predicate) { + start = start || 0; + length = length || array.length; + this.hasNext = true; // Use to identify if there's another element. + this.peek = null; // Use for look-ahead + this.start_ = start; + this.array_ = array; + this.predicate_ = predicate; + this.end_ = Math.min(array.length, start + length); + this.nextIdx_ = start - 1; // use -1 so initial advance works. + this.next(); // ignoring result. +} + +; + +/** + * @return {Object} + */ +Iterator.prototype.next = function () { + if (!this.hasNext) { + return null; + } + var obj = this.peek; + + var nextIdx = this.nextIdx_ + 1; + var found = false; + while (nextIdx < this.end_) { + if (!this.predicate_ || this.predicate_(this.array_, nextIdx)) { + this.peek = this.array_[nextIdx]; + found = true; + break; + } + nextIdx++; + } + this.nextIdx_ = nextIdx; + if (!found) { + this.hasNext = false; + this.peek = null; + } + return obj; +}; + +/** + * Returns a new iterator over array, between indexes start and + * start + length, and only returns entries that pass the accept function + * + * @param {!Array} array the array to iterate over. + * @param {number} start the first index to iterate over, 0 if absent. + * @param {number} length the number of elements in the array to iterate over. + * This, along with start, defines a slice of the array, and so length + * doesn't imply the number of elements in the iterator when accept doesn't + * always accept all values. array.length when absent. + * @param {function(?):boolean=} opt_predicate a function that takes + * parameters array and idx, which returns true when the element should be + * returned. If omitted, all elements are accepted. + * @private + */ + +function createIterator(array, start, length, opt_predicate) { + return new Iterator(array, start, length, opt_predicate); +} + +; + +// Shim layer with setTimeout fallback. +// From: http://paulirish.com/2011/requestanimationframe-for-smart-animating/ +// Should be called with the window context: +// Dygraph.requestAnimFrame.call(window, function() {}) +var requestAnimFrame = (function () { + return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { + window.setTimeout(callback, 1000 / 60); + }; +})(); + +exports.requestAnimFrame = requestAnimFrame; +/** + * Call a function at most maxFrames times at an attempted interval of + * framePeriodInMillis, then call a cleanup function once. repeatFn is called + * once immediately, then at most (maxFrames - 1) times asynchronously. If + * maxFrames==1, then cleanup_fn() is also called synchronously. This function + * is used to sequence animation. + * @param {function(number)} repeatFn Called repeatedly -- takes the frame + * number (from 0 to maxFrames-1) as an argument. + * @param {number} maxFrames The max number of times to call repeatFn + * @param {number} framePeriodInMillis Max requested time between frames. + * @param {function()} cleanupFn A function to call after all repeatFn calls. + * @private + */ + +function repeatAndCleanup(repeatFn, maxFrames, framePeriodInMillis, cleanupFn) { + var frameNumber = 0; + var previousFrameNumber; + var startTime = new Date().getTime(); + repeatFn(frameNumber); + if (maxFrames == 1) { + cleanupFn(); + return; + } + var maxFrameArg = maxFrames - 1; + + (function loop() { + if (frameNumber >= maxFrames) return; + requestAnimFrame.call(window, function () { + // Determine which frame to draw based on the delay so far. Will skip + // frames if necessary. + var currentTime = new Date().getTime(); + var delayInMillis = currentTime - startTime; + previousFrameNumber = frameNumber; + frameNumber = Math.floor(delayInMillis / framePeriodInMillis); + var frameDelta = frameNumber - previousFrameNumber; + // If we predict that the subsequent repeatFn call will overshoot our + // total frame target, so our last call will cause a stutter, then jump to + // the last call immediately. If we're going to cause a stutter, better + // to do it faster than slower. + var predictOvershootStutter = frameNumber + frameDelta > maxFrameArg; + if (predictOvershootStutter || frameNumber >= maxFrameArg) { + repeatFn(maxFrameArg); // Ensure final call with maxFrameArg. + cleanupFn(); + } else { + if (frameDelta !== 0) { + // Don't call repeatFn with duplicate frames. + repeatFn(frameNumber); + } + loop(); + } + }); + })(); +} + +; + +// A whitelist of options that do not change pixel positions. +var pixelSafeOptions = { + 'annotationClickHandler': true, + 'annotationDblClickHandler': true, + 'annotationMouseOutHandler': true, + 'annotationMouseOverHandler': true, + 'axisLineColor': true, + 'axisLineWidth': true, + 'clickCallback': true, + 'drawCallback': true, + 'drawHighlightPointCallback': true, + 'drawPoints': true, + 'drawPointCallback': true, + 'drawGrid': true, + 'fillAlpha': true, + 'gridLineColor': true, + 'gridLineWidth': true, + 'hideOverlayOnMouseOut': true, + 'highlightCallback': true, + 'highlightCircleSize': true, + 'interactionModel': true, + 'labelsDiv': true, + 'labelsKMB': true, + 'labelsKMG2': true, + 'labelsSeparateLines': true, + 'labelsShowZeroValues': true, + 'legend': true, + 'panEdgeFraction': true, + 'pixelsPerYLabel': true, + 'pointClickCallback': true, + 'pointSize': true, + 'rangeSelectorPlotFillColor': true, + 'rangeSelectorPlotFillGradientColor': true, + 'rangeSelectorPlotStrokeColor': true, + 'rangeSelectorBackgroundStrokeColor': true, + 'rangeSelectorBackgroundLineWidth': true, + 'rangeSelectorPlotLineWidth': true, + 'rangeSelectorForegroundStrokeColor': true, + 'rangeSelectorForegroundLineWidth': true, + 'rangeSelectorAlpha': true, + 'showLabelsOnHighlight': true, + 'showRoller': true, + 'strokeWidth': true, + 'underlayCallback': true, + 'unhighlightCallback': true, + 'zoomCallback': true +}; + +/** + * This function will scan the option list and determine if they + * require us to recalculate the pixel positions of each point. + * TODO: move this into dygraph-options.js + * @param {!Array.} labels a list of options to check. + * @param {!Object} attrs + * @return {boolean} true if the graph needs new points else false. + * @private + */ + +function isPixelChangingOptionList(labels, attrs) { + // Assume that we do not require new points. + // This will change to true if we actually do need new points. + + // Create a dictionary of series names for faster lookup. + // If there are no labels, then the dictionary stays empty. + var seriesNamesDictionary = {}; + if (labels) { + for (var i = 1; i < labels.length; i++) { + seriesNamesDictionary[labels[i]] = true; + } + } + + // Scan through a flat (i.e. non-nested) object of options. + // Returns true/false depending on whether new points are needed. + var scanFlatOptions = function scanFlatOptions(options) { + for (var property in options) { + if (options.hasOwnProperty(property) && !pixelSafeOptions[property]) { + return true; + } + } + return false; + }; + + // Iterate through the list of updated options. + for (var property in attrs) { + if (!attrs.hasOwnProperty(property)) continue; + + // Find out of this field is actually a series specific options list. + if (property == 'highlightSeriesOpts' || seriesNamesDictionary[property] && !attrs.series) { + // This property value is a list of options for this series. + if (scanFlatOptions(attrs[property])) return true; + } else if (property == 'series' || property == 'axes') { + // This is twice-nested options list. + var perSeries = attrs[property]; + for (var series in perSeries) { + if (perSeries.hasOwnProperty(series) && scanFlatOptions(perSeries[series])) { + return true; + } + } + } else { + // If this was not a series specific option list, check if it's a pixel + // changing property. + if (!pixelSafeOptions[property]) return true; + } + } + + return false; +} + +; + +var Circles = { + DEFAULT: function DEFAULT(g, name, ctx, canvasx, canvasy, color, radius) { + ctx.beginPath(); + ctx.fillStyle = color; + ctx.arc(canvasx, canvasy, radius, 0, 2 * Math.PI, false); + ctx.fill(); + } + // For more shapes, include extras/shapes.js +}; + +exports.Circles = Circles; +/** + * Determine whether |data| is delimited by CR, CRLF, LF, LFCR. + * @param {string} data + * @return {?string} the delimiter that was detected (or null on failure). + */ + +function detectLineDelimiter(data) { + for (var i = 0; i < data.length; i++) { + var code = data.charAt(i); + if (code === '\r') { + // Might actually be "\r\n". + if (i + 1 < data.length && data.charAt(i + 1) === '\n') { + return '\r\n'; + } + return code; + } + if (code === '\n') { + // Might actually be "\n\r". + if (i + 1 < data.length && data.charAt(i + 1) === '\r') { + return '\n\r'; + } + return code; + } + } + + return null; +} + +; + +/** + * Is one node contained by another? + * @param {Node} containee The contained node. + * @param {Node} container The container node. + * @return {boolean} Whether containee is inside (or equal to) container. + * @private + */ + +function isNodeContainedBy(containee, container) { + if (container === null || containee === null) { + return false; + } + var containeeNode = /** @type {Node} */containee; + while (containeeNode && containeeNode !== container) { + containeeNode = containeeNode.parentNode; + } + return containeeNode === container; +} + +; + +// This masks some numeric issues in older versions of Firefox, +// where 1.0/Math.pow(10,2) != Math.pow(10,-2). +/** @type {function(number,number):number} */ + +function pow(base, exp) { + if (exp < 0) { + return 1.0 / Math.pow(base, -exp); + } + return Math.pow(base, exp); +} + +; + +var RGBA_RE = /^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*([01](?:\.\d+)?))?\)$/; + +/** + * Helper for toRGB_ which parses strings of the form: + * rgb(123, 45, 67) + * rgba(123, 45, 67, 0.5) + * @return parsed {r,g,b,a?} tuple or null. + */ +function parseRGBA(rgbStr) { + var bits = RGBA_RE.exec(rgbStr); + if (!bits) return null; + var r = parseInt(bits[1], 10), + g = parseInt(bits[2], 10), + b = parseInt(bits[3], 10); + if (bits[4]) { + return { r: r, g: g, b: b, a: parseFloat(bits[4]) }; + } else { + return { r: r, g: g, b: b }; + } +} + +/** + * Converts any valid CSS color (hex, rgb(), named color) to an RGB tuple. + * + * @param {!string} colorStr Any valid CSS color string. + * @return {{r:number,g:number,b:number,a:number?}} Parsed RGB tuple. + * @private + */ + +function toRGB_(colorStr) { + // Strategy: First try to parse colorStr directly. This is fast & avoids DOM + // manipulation. If that fails (e.g. for named colors like 'red'), then + // create a hidden DOM element and parse its computed color. + var rgb = parseRGBA(colorStr); + if (rgb) return rgb; + + var div = document.createElement('div'); + div.style.backgroundColor = colorStr; + div.style.visibility = 'hidden'; + document.body.appendChild(div); + var rgbStr = window.getComputedStyle(div, null).backgroundColor; + document.body.removeChild(div); + return parseRGBA(rgbStr); +} + +; + +/** + * Checks whether the browser supports the <canvas> tag. + * @param {HTMLCanvasElement=} opt_canvasElement Pass a canvas element as an + * optimization if you have one. + * @return {boolean} Whether the browser supports canvas. + */ + +function isCanvasSupported(opt_canvasElement) { + try { + var canvas = opt_canvasElement || document.createElement("canvas"); + canvas.getContext("2d"); + } catch (e) { + return false; + } + return true; +} + +; + +/** + * Parses the value as a floating point number. This is like the parseFloat() + * built-in, but with a few differences: + * - the empty string is parsed as null, rather than NaN. + * - if the string cannot be parsed at all, an error is logged. + * If the string can't be parsed, this method returns null. + * @param {string} x The string to be parsed + * @param {number=} opt_line_no The line number from which the string comes. + * @param {string=} opt_line The text of the line from which the string comes. + */ + +function parseFloat_(x, opt_line_no, opt_line) { + var val = parseFloat(x); + if (!isNaN(val)) return val; + + // Try to figure out what happeend. + // If the value is the empty string, parse it as null. + if (/^ *$/.test(x)) return null; + + // If it was actually "NaN", return it as NaN. + if (/^ *nan *$/i.test(x)) return NaN; + + // Looks like a parsing error. + var msg = "Unable to parse '" + x + "' as a number"; + if (opt_line !== undefined && opt_line_no !== undefined) { + msg += " on line " + (1 + (opt_line_no || 0)) + " ('" + opt_line + "') of CSV."; + } + console.error(msg); + + return null; +} + +; + +// Label constants for the labelsKMB and labelsKMG2 options. +// (i.e. '100000' -> '100K') +var KMB_LABELS = ['K', 'M', 'B', 'T', 'Q']; +var KMG2_BIG_LABELS = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; +var KMG2_SMALL_LABELS = ['m', 'u', 'n', 'p', 'f', 'a', 'z', 'y']; + +/** + * @private + * Return a string version of a number. This respects the digitsAfterDecimal + * and maxNumberWidth options. + * @param {number} x The number to be formatted + * @param {Dygraph} opts An options view + */ + +function numberValueFormatter(x, opts) { + var sigFigs = opts('sigFigs'); + + if (sigFigs !== null) { + // User has opted for a fixed number of significant figures. + return floatFormat(x, sigFigs); + } + + var digits = opts('digitsAfterDecimal'); + var maxNumberWidth = opts('maxNumberWidth'); + + var kmb = opts('labelsKMB'); + var kmg2 = opts('labelsKMG2'); + + var label; + + // switch to scientific notation if we underflow or overflow fixed display. + if (x !== 0.0 && (Math.abs(x) >= Math.pow(10, maxNumberWidth) || Math.abs(x) < Math.pow(10, -digits))) { + label = x.toExponential(digits); + } else { + label = '' + round_(x, digits); + } + + if (kmb || kmg2) { + var k; + var k_labels = []; + var m_labels = []; + if (kmb) { + k = 1000; + k_labels = KMB_LABELS; + } + if (kmg2) { + if (kmb) console.warn("Setting both labelsKMB and labelsKMG2. Pick one!"); + k = 1024; + k_labels = KMG2_BIG_LABELS; + m_labels = KMG2_SMALL_LABELS; + } + + var absx = Math.abs(x); + var n = pow(k, k_labels.length); + for (var j = k_labels.length - 1; j >= 0; j--, n /= k) { + if (absx >= n) { + label = round_(x / n, digits) + k_labels[j]; + break; + } + } + if (kmg2) { + // TODO(danvk): clean up this logic. Why so different than kmb? + var x_parts = String(x.toExponential()).split('e-'); + if (x_parts.length === 2 && x_parts[1] >= 3 && x_parts[1] <= 24) { + if (x_parts[1] % 3 > 0) { + label = round_(x_parts[0] / pow(10, x_parts[1] % 3), digits); + } else { + label = Number(x_parts[0]).toFixed(2); + } + label += m_labels[Math.floor(x_parts[1] / 3) - 1]; + } + } + } + + return label; +} + +; + +/** + * variant for use as an axisLabelFormatter. + * @private + */ + +function numberAxisLabelFormatter(x, granularity, opts) { + return numberValueFormatter.call(this, x, opts); +} + +; + +/** + * @type {!Array.} + * @private + * @constant + */ +var SHORT_MONTH_NAMES_ = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + +/** + * Convert a JS date to a string appropriate to display on an axis that + * is displaying values at the stated granularity. This respects the + * labelsUTC option. + * @param {Date} date The date to format + * @param {number} granularity One of the Dygraph granularity constants + * @param {Dygraph} opts An options view + * @return {string} The date formatted as local time + * @private + */ + +function dateAxisLabelFormatter(date, granularity, opts) { + var utc = opts('labelsUTC'); + var accessors = utc ? DateAccessorsUTC : DateAccessorsLocal; + + var year = accessors.getFullYear(date), + month = accessors.getMonth(date), + day = accessors.getDate(date), + hours = accessors.getHours(date), + mins = accessors.getMinutes(date), + secs = accessors.getSeconds(date), + millis = accessors.getMilliseconds(date); + + if (granularity >= DygraphTickers.Granularity.DECADAL) { + return '' + year; + } else if (granularity >= DygraphTickers.Granularity.MONTHLY) { + return SHORT_MONTH_NAMES_[month] + ' ' + year; + } else { + var frac = hours * 3600 + mins * 60 + secs + 1e-3 * millis; + if (frac === 0 || granularity >= DygraphTickers.Granularity.DAILY) { + // e.g. '21 Jan' (%d%b) + return zeropad(day) + ' ' + SHORT_MONTH_NAMES_[month]; + } else if (granularity < DygraphTickers.Granularity.SECONDLY) { + // e.g. 40.310 (meaning 40 seconds and 310 milliseconds) + var str = "" + millis; + return zeropad(secs) + "." + ('000' + str).substring(str.length); + } else if (granularity > DygraphTickers.Granularity.MINUTELY) { + return hmsString_(hours, mins, secs, 0); + } else { + return hmsString_(hours, mins, secs, millis); + } + } +} + +; +// alias in case anyone is referencing the old method. +// Dygraph.dateAxisFormatter = Dygraph.dateAxisLabelFormatter; + +/** + * Return a string version of a JS date for a value label. This respects the + * labelsUTC option. + * @param {Date} date The date to be formatted + * @param {Dygraph} opts An options view + * @private + */ + +function dateValueFormatter(d, opts) { + return dateString_(d, opts('labelsUTC')); +} + +; + +},{"./dygraph-tickers":16}],18:[function(require,module,exports){ +(function (process){ +/** + * @license + * Copyright 2006 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ /** + * @fileoverview Creates an interactive, zoomable graph based on a CSV file or + * string. Dygraph can handle multiple series with or without error bars. The + * date/value ranges will be automatically set. Dygraph uses the + * <canvas> tag, so it only works in FF1.5+. + * @author danvdk@gmail.com (Dan Vanderkam) + + Usage: +
+ + + The CSV file is of the form + + Date,SeriesA,SeriesB,SeriesC + YYYYMMDD,A1,B1,C1 + YYYYMMDD,A2,B2,C2 + + If the 'errorBars' option is set in the constructor, the input should be of + the form + Date,SeriesA,SeriesB,... + YYYYMMDD,A1,sigmaA1,B1,sigmaB1,... + YYYYMMDD,A2,sigmaA2,B2,sigmaB2,... + + If the 'fractions' option is set, the input should be of the form: + + Date,SeriesA,SeriesB,... + YYYYMMDD,A1/B1,A2/B2,... + YYYYMMDD,A1/B1,A2/B2,... + + And error bars will be calculated automatically using a binomial distribution. + + For further documentation and examples, see http://dygraphs.com/ + */'use strict';Object.defineProperty(exports,'__esModule',{value:true});var _slicedToArray=(function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n = (_s = _i.next()).done);_n = true) {_arr.push(_s.value);if(i && _arr.length === i)break;}}catch(err) {_d = true;_e = err;}finally {try{if(!_n && _i['return'])_i['return']();}finally {if(_d)throw _e;}}return _arr;}return function(arr,i){if(Array.isArray(arr)){return arr;}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i);}else {throw new TypeError('Invalid attempt to destructure non-iterable instance');}};})();function _interopRequireWildcard(obj){if(obj && obj.__esModule){return obj;}else {var newObj={};if(obj != null){for(var key in obj) {if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key] = obj[key];}}newObj['default'] = obj;return newObj;}}function _interopRequireDefault(obj){return obj && obj.__esModule?obj:{'default':obj};}var _dygraphLayout=require('./dygraph-layout');var _dygraphLayout2=_interopRequireDefault(_dygraphLayout);var _dygraphCanvas=require('./dygraph-canvas');var _dygraphCanvas2=_interopRequireDefault(_dygraphCanvas);var _dygraphOptions=require('./dygraph-options');var _dygraphOptions2=_interopRequireDefault(_dygraphOptions);var _dygraphInteractionModel=require('./dygraph-interaction-model');var _dygraphInteractionModel2=_interopRequireDefault(_dygraphInteractionModel);var _dygraphTickers=require('./dygraph-tickers');var DygraphTickers=_interopRequireWildcard(_dygraphTickers);var _dygraphUtils=require('./dygraph-utils');var utils=_interopRequireWildcard(_dygraphUtils);var _dygraphDefaultAttrs=require('./dygraph-default-attrs');var _dygraphDefaultAttrs2=_interopRequireDefault(_dygraphDefaultAttrs);var _dygraphOptionsReference=require('./dygraph-options-reference');var _dygraphOptionsReference2=_interopRequireDefault(_dygraphOptionsReference);var _iframeTarp=require('./iframe-tarp');var _iframeTarp2=_interopRequireDefault(_iframeTarp);var _datahandlerDefault=require('./datahandler/default');var _datahandlerDefault2=_interopRequireDefault(_datahandlerDefault);var _datahandlerBarsError=require('./datahandler/bars-error');var _datahandlerBarsError2=_interopRequireDefault(_datahandlerBarsError);var _datahandlerBarsCustom=require('./datahandler/bars-custom');var _datahandlerBarsCustom2=_interopRequireDefault(_datahandlerBarsCustom);var _datahandlerDefaultFractions=require('./datahandler/default-fractions');var _datahandlerDefaultFractions2=_interopRequireDefault(_datahandlerDefaultFractions);var _datahandlerBarsFractions=require('./datahandler/bars-fractions');var _datahandlerBarsFractions2=_interopRequireDefault(_datahandlerBarsFractions);var _datahandlerBars=require('./datahandler/bars');var _datahandlerBars2=_interopRequireDefault(_datahandlerBars);var _pluginsAnnotations=require('./plugins/annotations');var _pluginsAnnotations2=_interopRequireDefault(_pluginsAnnotations);var _pluginsAxes=require('./plugins/axes');var _pluginsAxes2=_interopRequireDefault(_pluginsAxes);var _pluginsChartLabels=require('./plugins/chart-labels');var _pluginsChartLabels2=_interopRequireDefault(_pluginsChartLabels);var _pluginsGrid=require('./plugins/grid');var _pluginsGrid2=_interopRequireDefault(_pluginsGrid);var _pluginsLegend=require('./plugins/legend');var _pluginsLegend2=_interopRequireDefault(_pluginsLegend);var _pluginsRangeSelector=require('./plugins/range-selector');var _pluginsRangeSelector2=_interopRequireDefault(_pluginsRangeSelector);var _dygraphGviz=require('./dygraph-gviz');var _dygraphGviz2=_interopRequireDefault(_dygraphGviz);"use strict"; /** + * Creates an interactive, zoomable chart. + * + * @constructor + * @param {div | String} div A div or the id of a div into which to construct + * the chart. + * @param {String | Function} file A file containing CSV data or a function + * that returns this data. The most basic expected format for each line is + * "YYYY/MM/DD,val1,val2,...". For more information, see + * http://dygraphs.com/data.html. + * @param {Object} attrs Various other attributes, e.g. errorBars determines + * whether the input data contains error ranges. For a complete list of + * options, see http://dygraphs.com/options.html. + */var Dygraph=function Dygraph(div,data,opts){this.__init__(div,data,opts);};Dygraph.NAME = "Dygraph";Dygraph.VERSION = "2.1.0"; // Various default values +Dygraph.DEFAULT_ROLL_PERIOD = 1;Dygraph.DEFAULT_WIDTH = 480;Dygraph.DEFAULT_HEIGHT = 320; // For max 60 Hz. animation: +Dygraph.ANIMATION_STEPS = 12;Dygraph.ANIMATION_DURATION = 200; /** + * Standard plotters. These may be used by clients. + * Available plotters are: + * - Dygraph.Plotters.linePlotter: draws central lines (most common) + * - Dygraph.Plotters.errorPlotter: draws error bars + * - Dygraph.Plotters.fillPlotter: draws fills under lines (used with fillGraph) + * + * By default, the plotter is [fillPlotter, errorPlotter, linePlotter]. + * This causes all the lines to be drawn over all the fills/error bars. + */Dygraph.Plotters = _dygraphCanvas2['default']._Plotters; // Used for initializing annotation CSS rules only once. +Dygraph.addedAnnotationCSS = false; /** + * Initializes the Dygraph. This creates a new DIV and constructs the PlotKit + * and context <canvas> inside of it. See the constructor for details. + * on the parameters. + * @param {Element} div the Element to render the graph into. + * @param {string | Function} file Source data + * @param {Object} attrs Miscellaneous other options + * @private + */Dygraph.prototype.__init__ = function(div,file,attrs){this.is_initial_draw_ = true;this.readyFns_ = []; // Support two-argument constructor +if(attrs === null || attrs === undefined){attrs = {};}attrs = Dygraph.copyUserAttrs_(attrs);if(typeof div == 'string'){div = document.getElementById(div);}if(!div){throw new Error('Constructing dygraph with a non-existent div!');} // Copy the important bits into the object +// TODO(danvk): most of these should just stay in the attrs_ dictionary. +this.maindiv_ = div;this.file_ = file;this.rollPeriod_ = attrs.rollPeriod || Dygraph.DEFAULT_ROLL_PERIOD;this.previousVerticalX_ = -1;this.fractions_ = attrs.fractions || false;this.dateWindow_ = attrs.dateWindow || null;this.annotations_ = []; // Clear the div. This ensure that, if multiple dygraphs are passed the same +// div, then only one will be drawn. +div.innerHTML = ""; // For historical reasons, the 'width' and 'height' options trump all CSS +// rules _except_ for an explicit 'width' or 'height' on the div. +// As an added convenience, if the div has zero height (like
does +// without any styles), then we use a default height/width. +if(div.style.width === '' && attrs.width){div.style.width = attrs.width + "px";}if(div.style.height === '' && attrs.height){div.style.height = attrs.height + "px";}if(div.style.height === '' && div.clientHeight === 0){div.style.height = Dygraph.DEFAULT_HEIGHT + "px";if(div.style.width === ''){div.style.width = Dygraph.DEFAULT_WIDTH + "px";}} // These will be zero if the dygraph's div is hidden. In that case, +// use the user-specified attributes if present. If not, use zero +// and assume the user will call resize to fix things later. +this.width_ = div.clientWidth || attrs.width || 0;this.height_ = div.clientHeight || attrs.height || 0; // TODO(danvk): set fillGraph to be part of attrs_ here, not user_attrs_. +if(attrs.stackedGraph){attrs.fillGraph = true; // TODO(nikhilk): Add any other stackedGraph checks here. +} // DEPRECATION WARNING: All option processing should be moved from +// attrs_ and user_attrs_ to options_, which holds all this information. +// +// Dygraphs has many options, some of which interact with one another. +// To keep track of everything, we maintain two sets of options: +// +// this.user_attrs_ only options explicitly set by the user. +// this.attrs_ defaults, options derived from user_attrs_, data. +// +// Options are then accessed this.attr_('attr'), which first looks at +// user_attrs_ and then computed attrs_. This way Dygraphs can set intelligent +// defaults without overriding behavior that the user specifically asks for. +this.user_attrs_ = {};utils.update(this.user_attrs_,attrs); // This sequence ensures that Dygraph.DEFAULT_ATTRS is never modified. +this.attrs_ = {};utils.updateDeep(this.attrs_,_dygraphDefaultAttrs2['default']);this.boundaryIds_ = [];this.setIndexByName_ = {};this.datasetIndex_ = [];this.registeredEvents_ = [];this.eventListeners_ = {};this.attributes_ = new _dygraphOptions2['default'](this); // Create the containing DIV and other interactive elements +this.createInterface_(); // Activate plugins. +this.plugins_ = [];var plugins=Dygraph.PLUGINS.concat(this.getOption('plugins'));for(var i=0;i < plugins.length;i++) { // the plugins option may contain either plugin classes or instances. +// Plugin instances contain an activate method. +var Plugin=plugins[i]; // either a constructor or an instance. +var pluginInstance;if(typeof Plugin.activate !== 'undefined'){pluginInstance = Plugin;}else {pluginInstance = new Plugin();}var pluginDict={plugin:pluginInstance,events:{},options:{},pluginOptions:{}};var handlers=pluginInstance.activate(this);for(var eventName in handlers) {if(!handlers.hasOwnProperty(eventName))continue; // TODO(danvk): validate eventName. +pluginDict.events[eventName] = handlers[eventName];}this.plugins_.push(pluginDict);} // At this point, plugins can no longer register event handlers. +// Construct a map from event -> ordered list of [callback, plugin]. +for(var i=0;i < this.plugins_.length;i++) {var plugin_dict=this.plugins_[i];for(var eventName in plugin_dict.events) {if(!plugin_dict.events.hasOwnProperty(eventName))continue;var callback=plugin_dict.events[eventName];var pair=[plugin_dict.plugin,callback];if(!(eventName in this.eventListeners_)){this.eventListeners_[eventName] = [pair];}else {this.eventListeners_[eventName].push(pair);}}}this.createDragInterface_();this.start_();}; /** + * Triggers a cascade of events to the various plugins which are interested in them. + * Returns true if the "default behavior" should be prevented, i.e. if one + * of the event listeners called event.preventDefault(). + * @private + */Dygraph.prototype.cascadeEvents_ = function(name,extra_props){if(!(name in this.eventListeners_))return false; // QUESTION: can we use objects & prototypes to speed this up? +var e={dygraph:this,cancelable:false,defaultPrevented:false,preventDefault:function preventDefault(){if(!e.cancelable)throw "Cannot call preventDefault on non-cancelable event.";e.defaultPrevented = true;},propagationStopped:false,stopPropagation:function stopPropagation(){e.propagationStopped = true;}};utils.update(e,extra_props);var callback_plugin_pairs=this.eventListeners_[name];if(callback_plugin_pairs){for(var i=callback_plugin_pairs.length - 1;i >= 0;i--) {var plugin=callback_plugin_pairs[i][0];var callback=callback_plugin_pairs[i][1];callback.call(plugin,e);if(e.propagationStopped)break;}}return e.defaultPrevented;}; /** + * Fetch a plugin instance of a particular class. Only for testing. + * @private + * @param {!Class} type The type of the plugin. + * @return {Object} Instance of the plugin, or null if there is none. + */Dygraph.prototype.getPluginInstance_ = function(type){for(var i=0;i < this.plugins_.length;i++) {var p=this.plugins_[i];if(p.plugin instanceof type){return p.plugin;}}return null;}; /** + * Returns the zoomed status of the chart for one or both axes. + * + * Axis is an optional parameter. Can be set to 'x' or 'y'. + * + * The zoomed status for an axis is set whenever a user zooms using the mouse + * or when the dateWindow or valueRange are updated. Double-clicking or calling + * resetZoom() resets the zoom status for the chart. + */Dygraph.prototype.isZoomed = function(axis){var isZoomedX=!!this.dateWindow_;if(axis === 'x')return isZoomedX;var isZoomedY=this.axes_.map(function(axis){return !!axis.valueRange;}).indexOf(true) >= 0;if(axis === null || axis === undefined){return isZoomedX || isZoomedY;}if(axis === 'y')return isZoomedY;throw new Error('axis parameter is [' + axis + '] must be null, \'x\' or \'y\'.');}; /** + * Returns information about the Dygraph object, including its containing ID. + */Dygraph.prototype.toString = function(){var maindiv=this.maindiv_;var id=maindiv && maindiv.id?maindiv.id:maindiv;return "[Dygraph " + id + "]";}; /** + * @private + * Returns the value of an option. This may be set by the user (either in the + * constructor or by calling updateOptions) or by dygraphs, and may be set to a + * per-series value. + * @param {string} name The name of the option, e.g. 'rollPeriod'. + * @param {string} [seriesName] The name of the series to which the option + * will be applied. If no per-series value of this option is available, then + * the global value is returned. This is optional. + * @return { ... } The value of the option. + */Dygraph.prototype.attr_ = function(name,seriesName){ // For "production" code, this gets removed by uglifyjs. +if(typeof process !== 'undefined'){if("development" != 'production'){if(typeof _dygraphOptionsReference2['default'] === 'undefined'){console.error('Must include options reference JS for testing');}else if(!_dygraphOptionsReference2['default'].hasOwnProperty(name)){console.error('Dygraphs is using property ' + name + ', which has no ' + 'entry in the Dygraphs.OPTIONS_REFERENCE listing.'); // Only log this error once. +_dygraphOptionsReference2['default'][name] = true;}}}return seriesName?this.attributes_.getForSeries(name,seriesName):this.attributes_.get(name);}; /** + * Returns the current value for an option, as set in the constructor or via + * updateOptions. You may pass in an (optional) series name to get per-series + * values for the option. + * + * All values returned by this method should be considered immutable. If you + * modify them, there is no guarantee that the changes will be honored or that + * dygraphs will remain in a consistent state. If you want to modify an option, + * use updateOptions() instead. + * + * @param {string} name The name of the option (e.g. 'strokeWidth') + * @param {string=} opt_seriesName Series name to get per-series values. + * @return {*} The value of the option. + */Dygraph.prototype.getOption = function(name,opt_seriesName){return this.attr_(name,opt_seriesName);}; /** + * Like getOption(), but specifically returns a number. + * This is a convenience function for working with the Closure Compiler. + * @param {string} name The name of the option (e.g. 'strokeWidth') + * @param {string=} opt_seriesName Series name to get per-series values. + * @return {number} The value of the option. + * @private + */Dygraph.prototype.getNumericOption = function(name,opt_seriesName){return (/** @type{number} */this.getOption(name,opt_seriesName));}; /** + * Like getOption(), but specifically returns a string. + * This is a convenience function for working with the Closure Compiler. + * @param {string} name The name of the option (e.g. 'strokeWidth') + * @param {string=} opt_seriesName Series name to get per-series values. + * @return {string} The value of the option. + * @private + */Dygraph.prototype.getStringOption = function(name,opt_seriesName){return (/** @type{string} */this.getOption(name,opt_seriesName));}; /** + * Like getOption(), but specifically returns a boolean. + * This is a convenience function for working with the Closure Compiler. + * @param {string} name The name of the option (e.g. 'strokeWidth') + * @param {string=} opt_seriesName Series name to get per-series values. + * @return {boolean} The value of the option. + * @private + */Dygraph.prototype.getBooleanOption = function(name,opt_seriesName){return (/** @type{boolean} */this.getOption(name,opt_seriesName));}; /** + * Like getOption(), but specifically returns a function. + * This is a convenience function for working with the Closure Compiler. + * @param {string} name The name of the option (e.g. 'strokeWidth') + * @param {string=} opt_seriesName Series name to get per-series values. + * @return {function(...)} The value of the option. + * @private + */Dygraph.prototype.getFunctionOption = function(name,opt_seriesName){return (/** @type{function(...)} */this.getOption(name,opt_seriesName));};Dygraph.prototype.getOptionForAxis = function(name,axis){return this.attributes_.getForAxis(name,axis);}; /** + * @private + * @param {string} axis The name of the axis (i.e. 'x', 'y' or 'y2') + * @return { ... } A function mapping string -> option value + */Dygraph.prototype.optionsViewForAxis_ = function(axis){var self=this;return function(opt){var axis_opts=self.user_attrs_.axes;if(axis_opts && axis_opts[axis] && axis_opts[axis].hasOwnProperty(opt)){return axis_opts[axis][opt];} // I don't like that this is in a second spot. +if(axis === 'x' && opt === 'logscale'){ // return the default value. +// TODO(konigsberg): pull the default from a global default. +return false;} // user-specified attributes always trump defaults, even if they're less +// specific. +if(typeof self.user_attrs_[opt] != 'undefined'){return self.user_attrs_[opt];}axis_opts = self.attrs_.axes;if(axis_opts && axis_opts[axis] && axis_opts[axis].hasOwnProperty(opt)){return axis_opts[axis][opt];} // check old-style axis options +// TODO(danvk): add a deprecation warning if either of these match. +if(axis == 'y' && self.axes_[0].hasOwnProperty(opt)){return self.axes_[0][opt];}else if(axis == 'y2' && self.axes_[1].hasOwnProperty(opt)){return self.axes_[1][opt];}return self.attr_(opt);};}; /** + * Returns the current rolling period, as set by the user or an option. + * @return {number} The number of points in the rolling window + */Dygraph.prototype.rollPeriod = function(){return this.rollPeriod_;}; /** + * Returns the currently-visible x-range. This can be affected by zooming, + * panning or a call to updateOptions. + * Returns a two-element array: [left, right]. + * If the Dygraph has dates on the x-axis, these will be millis since epoch. + */Dygraph.prototype.xAxisRange = function(){return this.dateWindow_?this.dateWindow_:this.xAxisExtremes();}; /** + * Returns the lower- and upper-bound x-axis values of the data set. + */Dygraph.prototype.xAxisExtremes = function(){var pad=this.getNumericOption('xRangePad') / this.plotter_.area.w;if(this.numRows() === 0){return [0 - pad,1 + pad];}var left=this.rawData_[0][0];var right=this.rawData_[this.rawData_.length - 1][0];if(pad){ // Must keep this in sync with dygraph-layout _evaluateLimits() +var range=right - left;left -= range * pad;right += range * pad;}return [left,right];}; /** + * Returns the lower- and upper-bound y-axis values for each axis. These are + * the ranges you'll get if you double-click to zoom out or call resetZoom(). + * The return value is an array of [low, high] tuples, one for each y-axis. + */Dygraph.prototype.yAxisExtremes = function(){ // TODO(danvk): this is pretty inefficient +var packed=this.gatherDatasets_(this.rolledSeries_,null);var extremes=packed.extremes;var saveAxes=this.axes_;this.computeYAxisRanges_(extremes);var newAxes=this.axes_;this.axes_ = saveAxes;return newAxes.map(function(axis){return axis.extremeRange;});}; /** + * Returns the currently-visible y-range for an axis. This can be affected by + * zooming, panning or a call to updateOptions. Axis indices are zero-based. If + * called with no arguments, returns the range of the first axis. + * Returns a two-element array: [bottom, top]. + */Dygraph.prototype.yAxisRange = function(idx){if(typeof idx == "undefined")idx = 0;if(idx < 0 || idx >= this.axes_.length){return null;}var axis=this.axes_[idx];return [axis.computedValueRange[0],axis.computedValueRange[1]];}; /** + * Returns the currently-visible y-ranges for each axis. This can be affected by + * zooming, panning, calls to updateOptions, etc. + * Returns an array of [bottom, top] pairs, one for each y-axis. + */Dygraph.prototype.yAxisRanges = function(){var ret=[];for(var i=0;i < this.axes_.length;i++) {ret.push(this.yAxisRange(i));}return ret;}; // TODO(danvk): use these functions throughout dygraphs. +/** + * Convert from data coordinates to canvas/div X/Y coordinates. + * If specified, do this conversion for the coordinate system of a particular + * axis. Uses the first axis by default. + * Returns a two-element array: [X, Y] + * + * Note: use toDomXCoord instead of toDomCoords(x, null) and use toDomYCoord + * instead of toDomCoords(null, y, axis). + */Dygraph.prototype.toDomCoords = function(x,y,axis){return [this.toDomXCoord(x),this.toDomYCoord(y,axis)];}; /** + * Convert from data x coordinates to canvas/div X coordinate. + * If specified, do this conversion for the coordinate system of a particular + * axis. + * Returns a single value or null if x is null. + */Dygraph.prototype.toDomXCoord = function(x){if(x === null){return null;}var area=this.plotter_.area;var xRange=this.xAxisRange();return area.x + (x - xRange[0]) / (xRange[1] - xRange[0]) * area.w;}; /** + * Convert from data x coordinates to canvas/div Y coordinate and optional + * axis. Uses the first axis by default. + * + * returns a single value or null if y is null. + */Dygraph.prototype.toDomYCoord = function(y,axis){var pct=this.toPercentYCoord(y,axis);if(pct === null){return null;}var area=this.plotter_.area;return area.y + pct * area.h;}; /** + * Convert from canvas/div coords to data coordinates. + * If specified, do this conversion for the coordinate system of a particular + * axis. Uses the first axis by default. + * Returns a two-element array: [X, Y]. + * + * Note: use toDataXCoord instead of toDataCoords(x, null) and use toDataYCoord + * instead of toDataCoords(null, y, axis). + */Dygraph.prototype.toDataCoords = function(x,y,axis){return [this.toDataXCoord(x),this.toDataYCoord(y,axis)];}; /** + * Convert from canvas/div x coordinate to data coordinate. + * + * If x is null, this returns null. + */Dygraph.prototype.toDataXCoord = function(x){if(x === null){return null;}var area=this.plotter_.area;var xRange=this.xAxisRange();if(!this.attributes_.getForAxis("logscale",'x')){return xRange[0] + (x - area.x) / area.w * (xRange[1] - xRange[0]);}else {var pct=(x - area.x) / area.w;return utils.logRangeFraction(xRange[0],xRange[1],pct);}}; /** + * Convert from canvas/div y coord to value. + * + * If y is null, this returns null. + * if axis is null, this uses the first axis. + */Dygraph.prototype.toDataYCoord = function(y,axis){if(y === null){return null;}var area=this.plotter_.area;var yRange=this.yAxisRange(axis);if(typeof axis == "undefined")axis = 0;if(!this.attributes_.getForAxis("logscale",axis)){return yRange[0] + (area.y + area.h - y) / area.h * (yRange[1] - yRange[0]);}else { // Computing the inverse of toDomCoord. +var pct=(y - area.y) / area.h; // Note reversed yRange, y1 is on top with pct==0. +return utils.logRangeFraction(yRange[1],yRange[0],pct);}}; /** + * Converts a y for an axis to a percentage from the top to the + * bottom of the drawing area. + * + * If the coordinate represents a value visible on the canvas, then + * the value will be between 0 and 1, where 0 is the top of the canvas. + * However, this method will return values outside the range, as + * values can fall outside the canvas. + * + * If y is null, this returns null. + * if axis is null, this uses the first axis. + * + * @param {number} y The data y-coordinate. + * @param {number} [axis] The axis number on which the data coordinate lives. + * @return {number} A fraction in [0, 1] where 0 = the top edge. + */Dygraph.prototype.toPercentYCoord = function(y,axis){if(y === null){return null;}if(typeof axis == "undefined")axis = 0;var yRange=this.yAxisRange(axis);var pct;var logscale=this.attributes_.getForAxis("logscale",axis);if(logscale){var logr0=utils.log10(yRange[0]);var logr1=utils.log10(yRange[1]);pct = (logr1 - utils.log10(y)) / (logr1 - logr0);}else { // yRange[1] - y is unit distance from the bottom. +// yRange[1] - yRange[0] is the scale of the range. +// (yRange[1] - y) / (yRange[1] - yRange[0]) is the % from the bottom. +pct = (yRange[1] - y) / (yRange[1] - yRange[0]);}return pct;}; /** + * Converts an x value to a percentage from the left to the right of + * the drawing area. + * + * If the coordinate represents a value visible on the canvas, then + * the value will be between 0 and 1, where 0 is the left of the canvas. + * However, this method will return values outside the range, as + * values can fall outside the canvas. + * + * If x is null, this returns null. + * @param {number} x The data x-coordinate. + * @return {number} A fraction in [0, 1] where 0 = the left edge. + */Dygraph.prototype.toPercentXCoord = function(x){if(x === null){return null;}var xRange=this.xAxisRange();var pct;var logscale=this.attributes_.getForAxis("logscale",'x');if(logscale === true){ // logscale can be null so we test for true explicitly. +var logr0=utils.log10(xRange[0]);var logr1=utils.log10(xRange[1]);pct = (utils.log10(x) - logr0) / (logr1 - logr0);}else { // x - xRange[0] is unit distance from the left. +// xRange[1] - xRange[0] is the scale of the range. +// The full expression below is the % from the left. +pct = (x - xRange[0]) / (xRange[1] - xRange[0]);}return pct;}; /** + * Returns the number of columns (including the independent variable). + * @return {number} The number of columns. + */Dygraph.prototype.numColumns = function(){if(!this.rawData_)return 0;return this.rawData_[0]?this.rawData_[0].length:this.attr_("labels").length;}; /** + * Returns the number of rows (excluding any header/label row). + * @return {number} The number of rows, less any header. + */Dygraph.prototype.numRows = function(){if(!this.rawData_)return 0;return this.rawData_.length;}; /** + * Returns the value in the given row and column. If the row and column exceed + * the bounds on the data, returns null. Also returns null if the value is + * missing. + * @param {number} row The row number of the data (0-based). Row 0 is the + * first row of data, not a header row. + * @param {number} col The column number of the data (0-based) + * @return {number} The value in the specified cell or null if the row/col + * were out of range. + */Dygraph.prototype.getValue = function(row,col){if(row < 0 || row > this.rawData_.length)return null;if(col < 0 || col > this.rawData_[row].length)return null;return this.rawData_[row][col];}; /** + * Generates interface elements for the Dygraph: a containing div, a div to + * display the current point, and a textbox to adjust the rolling average + * period. Also creates the Renderer/Layout elements. + * @private + */Dygraph.prototype.createInterface_ = function(){ // Create the all-enclosing graph div +var enclosing=this.maindiv_;this.graphDiv = document.createElement("div"); // TODO(danvk): any other styles that are useful to set here? +this.graphDiv.style.textAlign = 'left'; // This is a CSS "reset" +this.graphDiv.style.position = 'relative';enclosing.appendChild(this.graphDiv); // Create the canvas for interactive parts of the chart. +this.canvas_ = utils.createCanvas();this.canvas_.style.position = "absolute"; // ... and for static parts of the chart. +this.hidden_ = this.createPlotKitCanvas_(this.canvas_);this.canvas_ctx_ = utils.getContext(this.canvas_);this.hidden_ctx_ = utils.getContext(this.hidden_);this.resizeElements_(); // The interactive parts of the graph are drawn on top of the chart. +this.graphDiv.appendChild(this.hidden_);this.graphDiv.appendChild(this.canvas_);this.mouseEventElement_ = this.createMouseEventElement_(); // Create the grapher +this.layout_ = new _dygraphLayout2['default'](this);var dygraph=this;this.mouseMoveHandler_ = function(e){dygraph.mouseMove_(e);};this.mouseOutHandler_ = function(e){ // The mouse has left the chart if: +// 1. e.target is inside the chart +// 2. e.relatedTarget is outside the chart +var target=e.target || e.fromElement;var relatedTarget=e.relatedTarget || e.toElement;if(utils.isNodeContainedBy(target,dygraph.graphDiv) && !utils.isNodeContainedBy(relatedTarget,dygraph.graphDiv)){dygraph.mouseOut_(e);}};this.addAndTrackEvent(window,'mouseout',this.mouseOutHandler_);this.addAndTrackEvent(this.mouseEventElement_,'mousemove',this.mouseMoveHandler_); // Don't recreate and register the resize handler on subsequent calls. +// This happens when the graph is resized. +if(!this.resizeHandler_){this.resizeHandler_ = function(e){dygraph.resize();}; // Update when the window is resized. +// TODO(danvk): drop frames depending on complexity of the chart. +this.addAndTrackEvent(window,'resize',this.resizeHandler_);}};Dygraph.prototype.resizeElements_ = function(){this.graphDiv.style.width = this.width_ + "px";this.graphDiv.style.height = this.height_ + "px";var pixelRatioOption=this.getNumericOption('pixelRatio');var canvasScale=pixelRatioOption || utils.getContextPixelRatio(this.canvas_ctx_);this.canvas_.width = this.width_ * canvasScale;this.canvas_.height = this.height_ * canvasScale;this.canvas_.style.width = this.width_ + "px"; // for IE +this.canvas_.style.height = this.height_ + "px"; // for IE +if(canvasScale !== 1){this.canvas_ctx_.scale(canvasScale,canvasScale);}var hiddenScale=pixelRatioOption || utils.getContextPixelRatio(this.hidden_ctx_);this.hidden_.width = this.width_ * hiddenScale;this.hidden_.height = this.height_ * hiddenScale;this.hidden_.style.width = this.width_ + "px"; // for IE +this.hidden_.style.height = this.height_ + "px"; // for IE +if(hiddenScale !== 1){this.hidden_ctx_.scale(hiddenScale,hiddenScale);}}; /** + * Detach DOM elements in the dygraph and null out all data references. + * Calling this when you're done with a dygraph can dramatically reduce memory + * usage. See, e.g., the tests/perf.html example. + */Dygraph.prototype.destroy = function(){this.canvas_ctx_.restore();this.hidden_ctx_.restore(); // Destroy any plugins, in the reverse order that they were registered. +for(var i=this.plugins_.length - 1;i >= 0;i--) {var p=this.plugins_.pop();if(p.plugin.destroy)p.plugin.destroy();}var removeRecursive=function removeRecursive(node){while(node.hasChildNodes()) {removeRecursive(node.firstChild);node.removeChild(node.firstChild);}};this.removeTrackedEvents_(); // remove mouse event handlers (This may not be necessary anymore) +utils.removeEvent(window,'mouseout',this.mouseOutHandler_);utils.removeEvent(this.mouseEventElement_,'mousemove',this.mouseMoveHandler_); // remove window handlers +utils.removeEvent(window,'resize',this.resizeHandler_);this.resizeHandler_ = null;removeRecursive(this.maindiv_);var nullOut=function nullOut(obj){for(var n in obj) {if(typeof obj[n] === 'object'){obj[n] = null;}}}; // These may not all be necessary, but it can't hurt... +nullOut(this.layout_);nullOut(this.plotter_);nullOut(this);}; /** + * Creates the canvas on which the chart will be drawn. Only the Renderer ever + * draws on this particular canvas. All Dygraph work (i.e. drawing hover dots + * or the zoom rectangles) is done on this.canvas_. + * @param {Object} canvas The Dygraph canvas over which to overlay the plot + * @return {Object} The newly-created canvas + * @private + */Dygraph.prototype.createPlotKitCanvas_ = function(canvas){var h=utils.createCanvas();h.style.position = "absolute"; // TODO(danvk): h should be offset from canvas. canvas needs to include +// some extra area to make it easier to zoom in on the far left and far +// right. h needs to be precisely the plot area, so that clipping occurs. +h.style.top = canvas.style.top;h.style.left = canvas.style.left;h.width = this.width_;h.height = this.height_;h.style.width = this.width_ + "px"; // for IE +h.style.height = this.height_ + "px"; // for IE +return h;}; /** + * Creates an overlay element used to handle mouse events. + * @return {Object} The mouse event element. + * @private + */Dygraph.prototype.createMouseEventElement_ = function(){return this.canvas_;}; /** + * Generate a set of distinct colors for the data series. This is done with a + * color wheel. Saturation/Value are customizable, and the hue is + * equally-spaced around the color wheel. If a custom set of colors is + * specified, that is used instead. + * @private + */Dygraph.prototype.setColors_ = function(){var labels=this.getLabels();var num=labels.length - 1;this.colors_ = [];this.colorsMap_ = {}; // These are used for when no custom colors are specified. +var sat=this.getNumericOption('colorSaturation') || 1.0;var val=this.getNumericOption('colorValue') || 0.5;var half=Math.ceil(num / 2);var colors=this.getOption('colors');var visibility=this.visibility();for(var i=0;i < num;i++) {if(!visibility[i]){continue;}var label=labels[i + 1];var colorStr=this.attributes_.getForSeries('color',label);if(!colorStr){if(colors){colorStr = colors[i % colors.length];}else { // alternate colors for high contrast. +var idx=i % 2?half + (i + 1) / 2:Math.ceil((i + 1) / 2);var hue=1.0 * idx / (1 + num);colorStr = utils.hsvToRGB(hue,sat,val);}}this.colors_.push(colorStr);this.colorsMap_[label] = colorStr;}}; /** + * Return the list of colors. This is either the list of colors passed in the + * attributes or the autogenerated list of rgb(r,g,b) strings. + * This does not return colors for invisible series. + * @return {Array.} The list of colors. + */Dygraph.prototype.getColors = function(){return this.colors_;}; /** + * Returns a few attributes of a series, i.e. its color, its visibility, which + * axis it's assigned to, and its column in the original data. + * Returns null if the series does not exist. + * Otherwise, returns an object with column, visibility, color and axis properties. + * The "axis" property will be set to 1 for y1 and 2 for y2. + * The "column" property can be fed back into getValue(row, column) to get + * values for this series. + */Dygraph.prototype.getPropertiesForSeries = function(series_name){var idx=-1;var labels=this.getLabels();for(var i=1;i < labels.length;i++) {if(labels[i] == series_name){idx = i;break;}}if(idx == -1)return null;return {name:series_name,column:idx,visible:this.visibility()[idx - 1],color:this.colorsMap_[series_name],axis:1 + this.attributes_.axisForSeries(series_name)};}; /** + * Create the text box to adjust the averaging period + * @private + */Dygraph.prototype.createRollInterface_ = function(){var _this=this; // Create a roller if one doesn't exist already. +var roller=this.roller_;if(!roller){this.roller_ = roller = document.createElement("input");roller.type = "text";roller.style.display = "none";roller.className = 'dygraph-roller';this.graphDiv.appendChild(roller);}var display=this.getBooleanOption('showRoller')?'block':'none';var area=this.getArea();var textAttr={"top":area.y + area.h - 25 + "px","left":area.x + 1 + "px","display":display};roller.size = "2";roller.value = this.rollPeriod_;utils.update(roller.style,textAttr);roller.onchange = function(){return _this.adjustRoll(roller.value);};}; /** + * Set up all the mouse handlers needed to capture dragging behavior for zoom + * events. + * @private + */Dygraph.prototype.createDragInterface_ = function(){var context={ // Tracks whether the mouse is down right now +isZooming:false,isPanning:false, // is this drag part of a pan? +is2DPan:false, // if so, is that pan 1- or 2-dimensional? +dragStartX:null, // pixel coordinates +dragStartY:null, // pixel coordinates +dragEndX:null, // pixel coordinates +dragEndY:null, // pixel coordinates +dragDirection:null,prevEndX:null, // pixel coordinates +prevEndY:null, // pixel coordinates +prevDragDirection:null,cancelNextDblclick:false, // see comment in dygraph-interaction-model.js +// The value on the left side of the graph when a pan operation starts. +initialLeftmostDate:null, // The number of units each pixel spans. (This won't be valid for log +// scales) +xUnitsPerPixel:null, // TODO(danvk): update this comment +// The range in second/value units that the viewport encompasses during a +// panning operation. +dateRange:null, // Top-left corner of the canvas, in DOM coords +// TODO(konigsberg): Rename topLeftCanvasX, topLeftCanvasY. +px:0,py:0, // Values for use with panEdgeFraction, which limit how far outside the +// graph's data boundaries it can be panned. +boundedDates:null, // [minDate, maxDate] +boundedValues:null, // [[minValue, maxValue] ...] +// We cover iframes during mouse interactions. See comments in +// dygraph-utils.js for more info on why this is a good idea. +tarp:new _iframeTarp2['default'](), // contextB is the same thing as this context object but renamed. +initializeMouseDown:function initializeMouseDown(event,g,contextB){ // prevents mouse drags from selecting page text. +if(event.preventDefault){event.preventDefault(); // Firefox, Chrome, etc. +}else {event.returnValue = false; // IE +event.cancelBubble = true;}var canvasPos=utils.findPos(g.canvas_);contextB.px = canvasPos.x;contextB.py = canvasPos.y;contextB.dragStartX = utils.dragGetX_(event,contextB);contextB.dragStartY = utils.dragGetY_(event,contextB);contextB.cancelNextDblclick = false;contextB.tarp.cover();},destroy:function destroy(){var context=this;if(context.isZooming || context.isPanning){context.isZooming = false;context.dragStartX = null;context.dragStartY = null;}if(context.isPanning){context.isPanning = false;context.draggingDate = null;context.dateRange = null;for(var i=0;i < self.axes_.length;i++) {delete self.axes_[i].draggingValue;delete self.axes_[i].dragValueRange;}}context.tarp.uncover();}};var interactionModel=this.getOption("interactionModel"); // Self is the graph. +var self=this; // Function that binds the graph and context to the handler. +var bindHandler=function bindHandler(handler){return function(event){handler(event,self,context);};};for(var eventName in interactionModel) {if(!interactionModel.hasOwnProperty(eventName))continue;this.addAndTrackEvent(this.mouseEventElement_,eventName,bindHandler(interactionModel[eventName]));} // If the user releases the mouse button during a drag, but not over the +// canvas, then it doesn't count as a zooming action. +if(!interactionModel.willDestroyContextMyself){var mouseUpHandler=function mouseUpHandler(event){context.destroy();};this.addAndTrackEvent(document,'mouseup',mouseUpHandler);}}; /** + * Draw a gray zoom rectangle over the desired area of the canvas. Also clears + * up any previous zoom rectangles that were drawn. This could be optimized to + * avoid extra redrawing, but it's tricky to avoid interactions with the status + * dots. + * + * @param {number} direction the direction of the zoom rectangle. Acceptable + * values are utils.HORIZONTAL and utils.VERTICAL. + * @param {number} startX The X position where the drag started, in canvas + * coordinates. + * @param {number} endX The current X position of the drag, in canvas coords. + * @param {number} startY The Y position where the drag started, in canvas + * coordinates. + * @param {number} endY The current Y position of the drag, in canvas coords. + * @param {number} prevDirection the value of direction on the previous call to + * this function. Used to avoid excess redrawing + * @param {number} prevEndX The value of endX on the previous call to this + * function. Used to avoid excess redrawing + * @param {number} prevEndY The value of endY on the previous call to this + * function. Used to avoid excess redrawing + * @private + */Dygraph.prototype.drawZoomRect_ = function(direction,startX,endX,startY,endY,prevDirection,prevEndX,prevEndY){var ctx=this.canvas_ctx_; // Clean up from the previous rect if necessary +if(prevDirection == utils.HORIZONTAL){ctx.clearRect(Math.min(startX,prevEndX),this.layout_.getPlotArea().y,Math.abs(startX - prevEndX),this.layout_.getPlotArea().h);}else if(prevDirection == utils.VERTICAL){ctx.clearRect(this.layout_.getPlotArea().x,Math.min(startY,prevEndY),this.layout_.getPlotArea().w,Math.abs(startY - prevEndY));} // Draw a light-grey rectangle to show the new viewing area +if(direction == utils.HORIZONTAL){if(endX && startX){ctx.fillStyle = "rgba(128,128,128,0.33)";ctx.fillRect(Math.min(startX,endX),this.layout_.getPlotArea().y,Math.abs(endX - startX),this.layout_.getPlotArea().h);}}else if(direction == utils.VERTICAL){if(endY && startY){ctx.fillStyle = "rgba(128,128,128,0.33)";ctx.fillRect(this.layout_.getPlotArea().x,Math.min(startY,endY),this.layout_.getPlotArea().w,Math.abs(endY - startY));}}}; /** + * Clear the zoom rectangle (and perform no zoom). + * @private + */Dygraph.prototype.clearZoomRect_ = function(){this.currentZoomRectArgs_ = null;this.canvas_ctx_.clearRect(0,0,this.width_,this.height_);}; /** + * Zoom to something containing [lowX, highX]. These are pixel coordinates in + * the canvas. The exact zoom window may be slightly larger if there are no data + * points near lowX or highX. Don't confuse this function with doZoomXDates, + * which accepts dates that match the raw data. This function redraws the graph. + * + * @param {number} lowX The leftmost pixel value that should be visible. + * @param {number} highX The rightmost pixel value that should be visible. + * @private + */Dygraph.prototype.doZoomX_ = function(lowX,highX){this.currentZoomRectArgs_ = null; // Find the earliest and latest dates contained in this canvasx range. +// Convert the call to date ranges of the raw data. +var minDate=this.toDataXCoord(lowX);var maxDate=this.toDataXCoord(highX);this.doZoomXDates_(minDate,maxDate);}; /** + * Zoom to something containing [minDate, maxDate] values. Don't confuse this + * method with doZoomX which accepts pixel coordinates. This function redraws + * the graph. + * + * @param {number} minDate The minimum date that should be visible. + * @param {number} maxDate The maximum date that should be visible. + * @private + */Dygraph.prototype.doZoomXDates_ = function(minDate,maxDate){var _this2=this; // TODO(danvk): when xAxisRange is null (i.e. "fit to data", the animation +// can produce strange effects. Rather than the x-axis transitioning slowly +// between values, it can jerk around.) +var old_window=this.xAxisRange();var new_window=[minDate,maxDate];var zoomCallback=this.getFunctionOption('zoomCallback');this.doAnimatedZoom(old_window,new_window,null,null,function(){if(zoomCallback){zoomCallback.call(_this2,minDate,maxDate,_this2.yAxisRanges());}});}; /** + * Zoom to something containing [lowY, highY]. These are pixel coordinates in + * the canvas. This function redraws the graph. + * + * @param {number} lowY The topmost pixel value that should be visible. + * @param {number} highY The lowest pixel value that should be visible. + * @private + */Dygraph.prototype.doZoomY_ = function(lowY,highY){var _this3=this;this.currentZoomRectArgs_ = null; // Find the highest and lowest values in pixel range for each axis. +// Note that lowY (in pixels) corresponds to the max Value (in data coords). +// This is because pixels increase as you go down on the screen, whereas data +// coordinates increase as you go up the screen. +var oldValueRanges=this.yAxisRanges();var newValueRanges=[];for(var i=0;i < this.axes_.length;i++) {var hi=this.toDataYCoord(lowY,i);var low=this.toDataYCoord(highY,i);newValueRanges.push([low,hi]);}var zoomCallback=this.getFunctionOption('zoomCallback');this.doAnimatedZoom(null,null,oldValueRanges,newValueRanges,function(){if(zoomCallback){var _xAxisRange=_this3.xAxisRange();var _xAxisRange2=_slicedToArray(_xAxisRange,2);var minX=_xAxisRange2[0];var maxX=_xAxisRange2[1];zoomCallback.call(_this3,minX,maxX,_this3.yAxisRanges());}});}; /** + * Transition function to use in animations. Returns values between 0.0 + * (totally old values) and 1.0 (totally new values) for each frame. + * @private + */Dygraph.zoomAnimationFunction = function(frame,numFrames){var k=1.5;return (1.0 - Math.pow(k,-frame)) / (1.0 - Math.pow(k,-numFrames));}; /** + * Reset the zoom to the original view coordinates. This is the same as + * double-clicking on the graph. + */Dygraph.prototype.resetZoom = function(){var _this4=this;var dirtyX=this.isZoomed('x');var dirtyY=this.isZoomed('y');var dirty=dirtyX || dirtyY; // Clear any selection, since it's likely to be drawn in the wrong place. +this.clearSelection();if(!dirty)return; // Calculate extremes to avoid lack of padding on reset. +var _xAxisExtremes=this.xAxisExtremes();var _xAxisExtremes2=_slicedToArray(_xAxisExtremes,2);var minDate=_xAxisExtremes2[0];var maxDate=_xAxisExtremes2[1];var animatedZooms=this.getBooleanOption('animatedZooms');var zoomCallback=this.getFunctionOption('zoomCallback'); // TODO(danvk): merge this block w/ the code below. +// TODO(danvk): factor out a generic, public zoomTo method. +if(!animatedZooms){this.dateWindow_ = null;this.axes_.forEach(function(axis){if(axis.valueRange)delete axis.valueRange;});this.drawGraph_();if(zoomCallback){zoomCallback.call(this,minDate,maxDate,this.yAxisRanges());}return;}var oldWindow=null,newWindow=null,oldValueRanges=null,newValueRanges=null;if(dirtyX){oldWindow = this.xAxisRange();newWindow = [minDate,maxDate];}if(dirtyY){oldValueRanges = this.yAxisRanges();newValueRanges = this.yAxisExtremes();}this.doAnimatedZoom(oldWindow,newWindow,oldValueRanges,newValueRanges,function(){_this4.dateWindow_ = null;_this4.axes_.forEach(function(axis){if(axis.valueRange)delete axis.valueRange;});if(zoomCallback){zoomCallback.call(_this4,minDate,maxDate,_this4.yAxisRanges());}});}; /** + * Combined animation logic for all zoom functions. + * either the x parameters or y parameters may be null. + * @private + */Dygraph.prototype.doAnimatedZoom = function(oldXRange,newXRange,oldYRanges,newYRanges,callback){var _this5=this;var steps=this.getBooleanOption("animatedZooms")?Dygraph.ANIMATION_STEPS:1;var windows=[];var valueRanges=[];var step,frac;if(oldXRange !== null && newXRange !== null){for(step = 1;step <= steps;step++) {frac = Dygraph.zoomAnimationFunction(step,steps);windows[step - 1] = [oldXRange[0] * (1 - frac) + frac * newXRange[0],oldXRange[1] * (1 - frac) + frac * newXRange[1]];}}if(oldYRanges !== null && newYRanges !== null){for(step = 1;step <= steps;step++) {frac = Dygraph.zoomAnimationFunction(step,steps);var thisRange=[];for(var j=0;j < this.axes_.length;j++) {thisRange.push([oldYRanges[j][0] * (1 - frac) + frac * newYRanges[j][0],oldYRanges[j][1] * (1 - frac) + frac * newYRanges[j][1]]);}valueRanges[step - 1] = thisRange;}}utils.repeatAndCleanup(function(step){if(valueRanges.length){for(var i=0;i < _this5.axes_.length;i++) {var w=valueRanges[step][i];_this5.axes_[i].valueRange = [w[0],w[1]];}}if(windows.length){_this5.dateWindow_ = windows[step];}_this5.drawGraph_();},steps,Dygraph.ANIMATION_DURATION / steps,callback);}; /** + * Get the current graph's area object. + * + * Returns: {x, y, w, h} + */Dygraph.prototype.getArea = function(){return this.plotter_.area;}; /** + * Convert a mouse event to DOM coordinates relative to the graph origin. + * + * Returns a two-element array: [X, Y]. + */Dygraph.prototype.eventToDomCoords = function(event){if(event.offsetX && event.offsetY){return [event.offsetX,event.offsetY];}else {var eventElementPos=utils.findPos(this.mouseEventElement_);var canvasx=utils.pageX(event) - eventElementPos.x;var canvasy=utils.pageY(event) - eventElementPos.y;return [canvasx,canvasy];}}; /** + * Given a canvas X coordinate, find the closest row. + * @param {number} domX graph-relative DOM X coordinate + * Returns {number} row number. + * @private + */Dygraph.prototype.findClosestRow = function(domX){var minDistX=Infinity;var closestRow=-1;var sets=this.layout_.points;for(var i=0;i < sets.length;i++) {var points=sets[i];var len=points.length;for(var j=0;j < len;j++) {var point=points[j];if(!utils.isValidPoint(point,true))continue;var dist=Math.abs(point.canvasx - domX);if(dist < minDistX){minDistX = dist;closestRow = point.idx;}}}return closestRow;}; /** + * Given canvas X,Y coordinates, find the closest point. + * + * This finds the individual data point across all visible series + * that's closest to the supplied DOM coordinates using the standard + * Euclidean X,Y distance. + * + * @param {number} domX graph-relative DOM X coordinate + * @param {number} domY graph-relative DOM Y coordinate + * Returns: {row, seriesName, point} + * @private + */Dygraph.prototype.findClosestPoint = function(domX,domY){var minDist=Infinity;var dist,dx,dy,point,closestPoint,closestSeries,closestRow;for(var setIdx=this.layout_.points.length - 1;setIdx >= 0;--setIdx) {var points=this.layout_.points[setIdx];for(var i=0;i < points.length;++i) {point = points[i];if(!utils.isValidPoint(point))continue;dx = point.canvasx - domX;dy = point.canvasy - domY;dist = dx * dx + dy * dy;if(dist < minDist){minDist = dist;closestPoint = point;closestSeries = setIdx;closestRow = point.idx;}}}var name=this.layout_.setNames[closestSeries];return {row:closestRow,seriesName:name,point:closestPoint};}; /** + * Given canvas X,Y coordinates, find the touched area in a stacked graph. + * + * This first finds the X data point closest to the supplied DOM X coordinate, + * then finds the series which puts the Y coordinate on top of its filled area, + * using linear interpolation between adjacent point pairs. + * + * @param {number} domX graph-relative DOM X coordinate + * @param {number} domY graph-relative DOM Y coordinate + * Returns: {row, seriesName, point} + * @private + */Dygraph.prototype.findStackedPoint = function(domX,domY){var row=this.findClosestRow(domX);var closestPoint,closestSeries;for(var setIdx=0;setIdx < this.layout_.points.length;++setIdx) {var boundary=this.getLeftBoundary_(setIdx);var rowIdx=row - boundary;var points=this.layout_.points[setIdx];if(rowIdx >= points.length)continue;var p1=points[rowIdx];if(!utils.isValidPoint(p1))continue;var py=p1.canvasy;if(domX > p1.canvasx && rowIdx + 1 < points.length){ // interpolate series Y value using next point +var p2=points[rowIdx + 1];if(utils.isValidPoint(p2)){var dx=p2.canvasx - p1.canvasx;if(dx > 0){var r=(domX - p1.canvasx) / dx;py += r * (p2.canvasy - p1.canvasy);}}}else if(domX < p1.canvasx && rowIdx > 0){ // interpolate series Y value using previous point +var p0=points[rowIdx - 1];if(utils.isValidPoint(p0)){var dx=p1.canvasx - p0.canvasx;if(dx > 0){var r=(p1.canvasx - domX) / dx;py += r * (p0.canvasy - p1.canvasy);}}} // Stop if the point (domX, py) is above this series' upper edge +if(setIdx === 0 || py < domY){closestPoint = p1;closestSeries = setIdx;}}var name=this.layout_.setNames[closestSeries];return {row:row,seriesName:name,point:closestPoint};}; /** + * When the mouse moves in the canvas, display information about a nearby data + * point and draw dots over those points in the data series. This function + * takes care of cleanup of previously-drawn dots. + * @param {Object} event The mousemove event from the browser. + * @private + */Dygraph.prototype.mouseMove_ = function(event){ // This prevents JS errors when mousing over the canvas before data loads. +var points=this.layout_.points;if(points === undefined || points === null)return;var canvasCoords=this.eventToDomCoords(event);var canvasx=canvasCoords[0];var canvasy=canvasCoords[1];var highlightSeriesOpts=this.getOption("highlightSeriesOpts");var selectionChanged=false;if(highlightSeriesOpts && !this.isSeriesLocked()){var closest;if(this.getBooleanOption("stackedGraph")){closest = this.findStackedPoint(canvasx,canvasy);}else {closest = this.findClosestPoint(canvasx,canvasy);}selectionChanged = this.setSelection(closest.row,closest.seriesName);}else {var idx=this.findClosestRow(canvasx);selectionChanged = this.setSelection(idx);}var callback=this.getFunctionOption("highlightCallback");if(callback && selectionChanged){callback.call(this,event,this.lastx_,this.selPoints_,this.lastRow_,this.highlightSet_);}}; /** + * Fetch left offset from the specified set index or if not passed, the + * first defined boundaryIds record (see bug #236). + * @private + */Dygraph.prototype.getLeftBoundary_ = function(setIdx){if(this.boundaryIds_[setIdx]){return this.boundaryIds_[setIdx][0];}else {for(var i=0;i < this.boundaryIds_.length;i++) {if(this.boundaryIds_[i] !== undefined){return this.boundaryIds_[i][0];}}return 0;}};Dygraph.prototype.animateSelection_ = function(direction){var totalSteps=10;var millis=30;if(this.fadeLevel === undefined)this.fadeLevel = 0;if(this.animateId === undefined)this.animateId = 0;var start=this.fadeLevel;var steps=direction < 0?start:totalSteps - start;if(steps <= 0){if(this.fadeLevel){this.updateSelection_(1.0);}return;}var thisId=++this.animateId;var that=this;var cleanupIfClearing=function cleanupIfClearing(){ // if we haven't reached fadeLevel 0 in the max frame time, +// ensure that the clear happens and just go to 0 +if(that.fadeLevel !== 0 && direction < 0){that.fadeLevel = 0;that.clearSelection();}};utils.repeatAndCleanup(function(n){ // ignore simultaneous animations +if(that.animateId != thisId)return;that.fadeLevel += direction;if(that.fadeLevel === 0){that.clearSelection();}else {that.updateSelection_(that.fadeLevel / totalSteps);}},steps,millis,cleanupIfClearing);}; /** + * Draw dots over the selectied points in the data series. This function + * takes care of cleanup of previously-drawn dots. + * @private + */Dygraph.prototype.updateSelection_ = function(opt_animFraction){ /*var defaultPrevented = */this.cascadeEvents_('select',{selectedRow:this.lastRow_ === -1?undefined:this.lastRow_,selectedX:this.lastx_ === -1?undefined:this.lastx_,selectedPoints:this.selPoints_}); // TODO(danvk): use defaultPrevented here? +// Clear the previously drawn vertical, if there is one +var i;var ctx=this.canvas_ctx_;if(this.getOption('highlightSeriesOpts')){ctx.clearRect(0,0,this.width_,this.height_);var alpha=1.0 - this.getNumericOption('highlightSeriesBackgroundAlpha');var backgroundColor=utils.toRGB_(this.getOption('highlightSeriesBackgroundColor'));if(alpha){ // Activating background fade includes an animation effect for a gradual +// fade. TODO(klausw): make this independently configurable if it causes +// issues? Use a shared preference to control animations? +var animateBackgroundFade=true;if(animateBackgroundFade){if(opt_animFraction === undefined){ // start a new animation +this.animateSelection_(1);return;}alpha *= opt_animFraction;}ctx.fillStyle = 'rgba(' + backgroundColor.r + ',' + backgroundColor.g + ',' + backgroundColor.b + ',' + alpha + ')';ctx.fillRect(0,0,this.width_,this.height_);} // Redraw only the highlighted series in the interactive canvas (not the +// static plot canvas, which is where series are usually drawn). +this.plotter_._renderLineChart(this.highlightSet_,ctx);}else if(this.previousVerticalX_ >= 0){ // Determine the maximum highlight circle size. +var maxCircleSize=0;var labels=this.attr_('labels');for(i = 1;i < labels.length;i++) {var r=this.getNumericOption('highlightCircleSize',labels[i]);if(r > maxCircleSize)maxCircleSize = r;}var px=this.previousVerticalX_;ctx.clearRect(px - maxCircleSize - 1,0,2 * maxCircleSize + 2,this.height_);}if(this.selPoints_.length > 0){ // Draw colored circles over the center of each selected point +var canvasx=this.selPoints_[0].canvasx;ctx.save();for(i = 0;i < this.selPoints_.length;i++) {var pt=this.selPoints_[i];if(isNaN(pt.canvasy))continue;var circleSize=this.getNumericOption('highlightCircleSize',pt.name);var callback=this.getFunctionOption("drawHighlightPointCallback",pt.name);var color=this.plotter_.colors[pt.name];if(!callback){callback = utils.Circles.DEFAULT;}ctx.lineWidth = this.getNumericOption('strokeWidth',pt.name);ctx.strokeStyle = color;ctx.fillStyle = color;callback.call(this,this,pt.name,ctx,canvasx,pt.canvasy,color,circleSize,pt.idx);}ctx.restore();this.previousVerticalX_ = canvasx;}}; /** + * Manually set the selected points and display information about them in the + * legend. The selection can be cleared using clearSelection() and queried + * using getSelection(). + * + * To set a selected series but not a selected point, call setSelection with + * row=false and the selected series name. + * + * @param {number} row Row number that should be highlighted (i.e. appear with + * hover dots on the chart). + * @param {seriesName} optional series name to highlight that series with the + * the highlightSeriesOpts setting. + * @param { locked } optional If true, keep seriesName selected when mousing + * over the graph, disabling closest-series highlighting. Call clearSelection() + * to unlock it. + */Dygraph.prototype.setSelection = function(row,opt_seriesName,opt_locked){ // Extract the points we've selected +this.selPoints_ = [];var changed=false;if(row !== false && row >= 0){if(row != this.lastRow_)changed = true;this.lastRow_ = row;for(var setIdx=0;setIdx < this.layout_.points.length;++setIdx) {var points=this.layout_.points[setIdx]; // Check if the point at the appropriate index is the point we're looking +// for. If it is, just use it, otherwise search the array for a point +// in the proper place. +var setRow=row - this.getLeftBoundary_(setIdx);if(setRow >= 0 && setRow < points.length && points[setRow].idx == row){var point=points[setRow];if(point.yval !== null)this.selPoints_.push(point);}else {for(var pointIdx=0;pointIdx < points.length;++pointIdx) {var point=points[pointIdx];if(point.idx == row){if(point.yval !== null){this.selPoints_.push(point);}break;}}}}}else {if(this.lastRow_ >= 0)changed = true;this.lastRow_ = -1;}if(this.selPoints_.length){this.lastx_ = this.selPoints_[0].xval;}else {this.lastx_ = -1;}if(opt_seriesName !== undefined){if(this.highlightSet_ !== opt_seriesName)changed = true;this.highlightSet_ = opt_seriesName;}if(opt_locked !== undefined){this.lockedSet_ = opt_locked;}if(changed){this.updateSelection_(undefined);}return changed;}; /** + * The mouse has left the canvas. Clear out whatever artifacts remain + * @param {Object} event the mouseout event from the browser. + * @private + */Dygraph.prototype.mouseOut_ = function(event){if(this.getFunctionOption("unhighlightCallback")){this.getFunctionOption("unhighlightCallback").call(this,event);}if(this.getBooleanOption("hideOverlayOnMouseOut") && !this.lockedSet_){this.clearSelection();}}; /** + * Clears the current selection (i.e. points that were highlighted by moving + * the mouse over the chart). + */Dygraph.prototype.clearSelection = function(){this.cascadeEvents_('deselect',{});this.lockedSet_ = false; // Get rid of the overlay data +if(this.fadeLevel){this.animateSelection_(-1);return;}this.canvas_ctx_.clearRect(0,0,this.width_,this.height_);this.fadeLevel = 0;this.selPoints_ = [];this.lastx_ = -1;this.lastRow_ = -1;this.highlightSet_ = null;}; /** + * Returns the number of the currently selected row. To get data for this row, + * you can use the getValue method. + * @return {number} row number, or -1 if nothing is selected + */Dygraph.prototype.getSelection = function(){if(!this.selPoints_ || this.selPoints_.length < 1){return -1;}for(var setIdx=0;setIdx < this.layout_.points.length;setIdx++) {var points=this.layout_.points[setIdx];for(var row=0;row < points.length;row++) {if(points[row].x == this.selPoints_[0].x){return points[row].idx;}}}return -1;}; /** + * Returns the name of the currently-highlighted series. + * Only available when the highlightSeriesOpts option is in use. + */Dygraph.prototype.getHighlightSeries = function(){return this.highlightSet_;}; /** + * Returns true if the currently-highlighted series was locked + * via setSelection(..., seriesName, true). + */Dygraph.prototype.isSeriesLocked = function(){return this.lockedSet_;}; /** + * Fires when there's data available to be graphed. + * @param {string} data Raw CSV data to be plotted + * @private + */Dygraph.prototype.loadedEvent_ = function(data){this.rawData_ = this.parseCSV_(data);this.cascadeDataDidUpdateEvent_();this.predraw_();}; /** + * Add ticks on the x-axis representing years, months, quarters, weeks, or days + * @private + */Dygraph.prototype.addXTicks_ = function(){ // Determine the correct ticks scale on the x-axis: quarterly, monthly, ... +var range;if(this.dateWindow_){range = [this.dateWindow_[0],this.dateWindow_[1]];}else {range = this.xAxisExtremes();}var xAxisOptionsView=this.optionsViewForAxis_('x');var xTicks=xAxisOptionsView('ticker')(range[0],range[1],this.plotter_.area.w, // TODO(danvk): should be area.width +xAxisOptionsView,this); // var msg = 'ticker(' + range[0] + ', ' + range[1] + ', ' + this.width_ + ', ' + this.attr_('pixelsPerXLabel') + ') -> ' + JSON.stringify(xTicks); +// console.log(msg); +this.layout_.setXTicks(xTicks);}; /** + * Returns the correct handler class for the currently set options. + * @private + */Dygraph.prototype.getHandlerClass_ = function(){var handlerClass;if(this.attr_('dataHandler')){handlerClass = this.attr_('dataHandler');}else if(this.fractions_){if(this.getBooleanOption('errorBars')){handlerClass = _datahandlerBarsFractions2['default'];}else {handlerClass = _datahandlerDefaultFractions2['default'];}}else if(this.getBooleanOption('customBars')){handlerClass = _datahandlerBarsCustom2['default'];}else if(this.getBooleanOption('errorBars')){handlerClass = _datahandlerBarsError2['default'];}else {handlerClass = _datahandlerDefault2['default'];}return handlerClass;}; /** + * @private + * This function is called once when the chart's data is changed or the options + * dictionary is updated. It is _not_ called when the user pans or zooms. The + * idea is that values derived from the chart's data can be computed here, + * rather than every time the chart is drawn. This includes things like the + * number of axes, rolling averages, etc. + */Dygraph.prototype.predraw_ = function(){var start=new Date(); // Create the correct dataHandler +this.dataHandler_ = new (this.getHandlerClass_())();this.layout_.computePlotArea(); // TODO(danvk): move more computations out of drawGraph_ and into here. +this.computeYAxes_();if(!this.is_initial_draw_){this.canvas_ctx_.restore();this.hidden_ctx_.restore();}this.canvas_ctx_.save();this.hidden_ctx_.save(); // Create a new plotter. +this.plotter_ = new _dygraphCanvas2['default'](this,this.hidden_,this.hidden_ctx_,this.layout_); // The roller sits in the bottom left corner of the chart. We don't know where +// this will be until the options are available, so it's positioned here. +this.createRollInterface_();this.cascadeEvents_('predraw'); // Convert the raw data (a 2D array) into the internal format and compute +// rolling averages. +this.rolledSeries_ = [null]; // x-axis is the first series and it's special +for(var i=1;i < this.numColumns();i++) { // var logScale = this.attr_('logscale', i); // TODO(klausw): this looks wrong // konigsberg thinks so too. +var series=this.dataHandler_.extractSeries(this.rawData_,i,this.attributes_);if(this.rollPeriod_ > 1){series = this.dataHandler_.rollingAverage(series,this.rollPeriod_,this.attributes_);}this.rolledSeries_.push(series);} // If the data or options have changed, then we'd better redraw. +this.drawGraph_(); // This is used to determine whether to do various animations. +var end=new Date();this.drawingTimeMs_ = end - start;}; /** + * Point structure. + * + * xval_* and yval_* are the original unscaled data values, + * while x_* and y_* are scaled to the range (0.0-1.0) for plotting. + * yval_stacked is the cumulative Y value used for stacking graphs, + * and bottom/top/minus/plus are used for error bar graphs. + * + * @typedef {{ + * idx: number, + * name: string, + * x: ?number, + * xval: ?number, + * y_bottom: ?number, + * y: ?number, + * y_stacked: ?number, + * y_top: ?number, + * yval_minus: ?number, + * yval: ?number, + * yval_plus: ?number, + * yval_stacked + * }} + */Dygraph.PointType = undefined; /** + * Calculates point stacking for stackedGraph=true. + * + * For stacking purposes, interpolate or extend neighboring data across + * NaN values based on stackedGraphNaNFill settings. This is for display + * only, the underlying data value as shown in the legend remains NaN. + * + * @param {Array.} points Point array for a single series. + * Updates each Point's yval_stacked property. + * @param {Array.} cumulativeYval Accumulated top-of-graph stacked Y + * values for the series seen so far. Index is the row number. Updated + * based on the current series's values. + * @param {Array.} seriesExtremes Min and max values, updated + * to reflect the stacked values. + * @param {string} fillMethod Interpolation method, one of 'all', 'inside', or + * 'none'. + * @private + */Dygraph.stackPoints_ = function(points,cumulativeYval,seriesExtremes,fillMethod){var lastXval=null;var prevPoint=null;var nextPoint=null;var nextPointIdx=-1; // Find the next stackable point starting from the given index. +var updateNextPoint=function updateNextPoint(idx){ // If we've previously found a non-NaN point and haven't gone past it yet, +// just use that. +if(nextPointIdx >= idx)return; // We haven't found a non-NaN point yet or have moved past it, +// look towards the right to find a non-NaN point. +for(var j=idx;j < points.length;++j) { // Clear out a previously-found point (if any) since it's no longer +// valid, we shouldn't use it for interpolation anymore. +nextPoint = null;if(!isNaN(points[j].yval) && points[j].yval !== null){nextPointIdx = j;nextPoint = points[j];break;}}};for(var i=0;i < points.length;++i) {var point=points[i];var xval=point.xval;if(cumulativeYval[xval] === undefined){cumulativeYval[xval] = 0;}var actualYval=point.yval;if(isNaN(actualYval) || actualYval === null){if(fillMethod == 'none'){actualYval = 0;}else { // Interpolate/extend for stacking purposes if possible. +updateNextPoint(i);if(prevPoint && nextPoint && fillMethod != 'none'){ // Use linear interpolation between prevPoint and nextPoint. +actualYval = prevPoint.yval + (nextPoint.yval - prevPoint.yval) * ((xval - prevPoint.xval) / (nextPoint.xval - prevPoint.xval));}else if(prevPoint && fillMethod == 'all'){actualYval = prevPoint.yval;}else if(nextPoint && fillMethod == 'all'){actualYval = nextPoint.yval;}else {actualYval = 0;}}}else {prevPoint = point;}var stackedYval=cumulativeYval[xval];if(lastXval != xval){ // If an x-value is repeated, we ignore the duplicates. +stackedYval += actualYval;cumulativeYval[xval] = stackedYval;}lastXval = xval;point.yval_stacked = stackedYval;if(stackedYval > seriesExtremes[1]){seriesExtremes[1] = stackedYval;}if(stackedYval < seriesExtremes[0]){seriesExtremes[0] = stackedYval;}}}; /** + * Loop over all fields and create datasets, calculating extreme y-values for + * each series and extreme x-indices as we go. + * + * dateWindow is passed in as an explicit parameter so that we can compute + * extreme values "speculatively", i.e. without actually setting state on the + * dygraph. + * + * @param {Array.)>>} rolledSeries, where + * rolledSeries[seriesIndex][row] = raw point, where + * seriesIndex is the column number starting with 1, and + * rawPoint is [x,y] or [x, [y, err]] or [x, [y, yminus, yplus]]. + * @param {?Array.} dateWindow [xmin, xmax] pair, or null. + * @return {{ + * points: Array.>, + * seriesExtremes: Array.>, + * boundaryIds: Array.}} + * @private + */Dygraph.prototype.gatherDatasets_ = function(rolledSeries,dateWindow){var boundaryIds=[];var points=[];var cumulativeYval=[]; // For stacked series. +var extremes={}; // series name -> [low, high] +var seriesIdx,sampleIdx;var firstIdx,lastIdx;var axisIdx; // Loop over the fields (series). Go from the last to the first, +// because if they're stacked that's how we accumulate the values. +var num_series=rolledSeries.length - 1;var series;for(seriesIdx = num_series;seriesIdx >= 1;seriesIdx--) {if(!this.visibility()[seriesIdx - 1])continue; // Prune down to the desired range, if necessary (for zooming) +// Because there can be lines going to points outside of the visible area, +// we actually prune to visible points, plus one on either side. +if(dateWindow){series = rolledSeries[seriesIdx];var low=dateWindow[0];var high=dateWindow[1]; // TODO(danvk): do binary search instead of linear search. +// TODO(danvk): pass firstIdx and lastIdx directly to the renderer. +firstIdx = null;lastIdx = null;for(sampleIdx = 0;sampleIdx < series.length;sampleIdx++) {if(series[sampleIdx][0] >= low && firstIdx === null){firstIdx = sampleIdx;}if(series[sampleIdx][0] <= high){lastIdx = sampleIdx;}}if(firstIdx === null)firstIdx = 0;var correctedFirstIdx=firstIdx;var isInvalidValue=true;while(isInvalidValue && correctedFirstIdx > 0) {correctedFirstIdx--; // check if the y value is null. +isInvalidValue = series[correctedFirstIdx][1] === null;}if(lastIdx === null)lastIdx = series.length - 1;var correctedLastIdx=lastIdx;isInvalidValue = true;while(isInvalidValue && correctedLastIdx < series.length - 1) {correctedLastIdx++;isInvalidValue = series[correctedLastIdx][1] === null;}if(correctedFirstIdx !== firstIdx){firstIdx = correctedFirstIdx;}if(correctedLastIdx !== lastIdx){lastIdx = correctedLastIdx;}boundaryIds[seriesIdx - 1] = [firstIdx,lastIdx]; // .slice's end is exclusive, we want to include lastIdx. +series = series.slice(firstIdx,lastIdx + 1);}else {series = rolledSeries[seriesIdx];boundaryIds[seriesIdx - 1] = [0,series.length - 1];}var seriesName=this.attr_("labels")[seriesIdx];var seriesExtremes=this.dataHandler_.getExtremeYValues(series,dateWindow,this.getBooleanOption("stepPlot",seriesName));var seriesPoints=this.dataHandler_.seriesToPoints(series,seriesName,boundaryIds[seriesIdx - 1][0]);if(this.getBooleanOption("stackedGraph")){axisIdx = this.attributes_.axisForSeries(seriesName);if(cumulativeYval[axisIdx] === undefined){cumulativeYval[axisIdx] = [];}Dygraph.stackPoints_(seriesPoints,cumulativeYval[axisIdx],seriesExtremes,this.getBooleanOption("stackedGraphNaNFill"));}extremes[seriesName] = seriesExtremes;points[seriesIdx] = seriesPoints;}return {points:points,extremes:extremes,boundaryIds:boundaryIds};}; /** + * Update the graph with new data. This method is called when the viewing area + * has changed. If the underlying data or options have changed, predraw_ will + * be called before drawGraph_ is called. + * + * @private + */Dygraph.prototype.drawGraph_ = function(){var start=new Date(); // This is used to set the second parameter to drawCallback, below. +var is_initial_draw=this.is_initial_draw_;this.is_initial_draw_ = false;this.layout_.removeAllDatasets();this.setColors_();this.attrs_.pointSize = 0.5 * this.getNumericOption('highlightCircleSize');var packed=this.gatherDatasets_(this.rolledSeries_,this.dateWindow_);var points=packed.points;var extremes=packed.extremes;this.boundaryIds_ = packed.boundaryIds;this.setIndexByName_ = {};var labels=this.attr_("labels");var dataIdx=0;for(var i=1;i < points.length;i++) {if(!this.visibility()[i - 1])continue;this.layout_.addDataset(labels[i],points[i]);this.datasetIndex_[i] = dataIdx++;}for(var i=0;i < labels.length;i++) {this.setIndexByName_[labels[i]] = i;}this.computeYAxisRanges_(extremes);this.layout_.setYAxes(this.axes_);this.addXTicks_(); // Tell PlotKit to use this new data and render itself +this.layout_.evaluate();this.renderGraph_(is_initial_draw);if(this.getStringOption("timingName")){var end=new Date();console.log(this.getStringOption("timingName") + " - drawGraph: " + (end - start) + "ms");}}; /** + * This does the work of drawing the chart. It assumes that the layout and axis + * scales have already been set (e.g. by predraw_). + * + * @private + */Dygraph.prototype.renderGraph_ = function(is_initial_draw){this.cascadeEvents_('clearChart');this.plotter_.clear();var underlayCallback=this.getFunctionOption('underlayCallback');if(underlayCallback){ // NOTE: we pass the dygraph object to this callback twice to avoid breaking +// users who expect a deprecated form of this callback. +underlayCallback.call(this,this.hidden_ctx_,this.layout_.getPlotArea(),this,this);}var e={canvas:this.hidden_,drawingContext:this.hidden_ctx_};this.cascadeEvents_('willDrawChart',e);this.plotter_.render();this.cascadeEvents_('didDrawChart',e);this.lastRow_ = -1; // because plugins/legend.js clears the legend +// TODO(danvk): is this a performance bottleneck when panning? +// The interaction canvas should already be empty in that situation. +this.canvas_.getContext('2d').clearRect(0,0,this.width_,this.height_);var drawCallback=this.getFunctionOption("drawCallback");if(drawCallback !== null){drawCallback.call(this,this,is_initial_draw);}if(is_initial_draw){this.readyFired_ = true;while(this.readyFns_.length > 0) {var fn=this.readyFns_.pop();fn(this);}}}; /** + * @private + * Determine properties of the y-axes which are independent of the data + * currently being displayed. This includes things like the number of axes and + * the style of the axes. It does not include the range of each axis and its + * tick marks. + * This fills in this.axes_. + * axes_ = [ { options } ] + * indices are into the axes_ array. + */Dygraph.prototype.computeYAxes_ = function(){var axis,index,opts,v; // this.axes_ doesn't match this.attributes_.axes_.options. It's used for +// data computation as well as options storage. +// Go through once and add all the axes. +this.axes_ = [];for(axis = 0;axis < this.attributes_.numAxes();axis++) { // Add a new axis, making a copy of its per-axis options. +opts = {g:this};utils.update(opts,this.attributes_.axisOptions(axis));this.axes_[axis] = opts;}for(axis = 0;axis < this.axes_.length;axis++) {if(axis === 0){opts = this.optionsViewForAxis_('y' + (axis?'2':''));v = opts("valueRange");if(v)this.axes_[axis].valueRange = v;}else { // To keep old behavior +var axes=this.user_attrs_.axes;if(axes && axes.y2){v = axes.y2.valueRange;if(v)this.axes_[axis].valueRange = v;}}}}; /** + * Returns the number of y-axes on the chart. + * @return {number} the number of axes. + */Dygraph.prototype.numAxes = function(){return this.attributes_.numAxes();}; /** + * @private + * Returns axis properties for the given series. + * @param {string} setName The name of the series for which to get axis + * properties, e.g. 'Y1'. + * @return {Object} The axis properties. + */Dygraph.prototype.axisPropertiesForSeries = function(series){ // TODO(danvk): handle errors. +return this.axes_[this.attributes_.axisForSeries(series)];}; /** + * @private + * Determine the value range and tick marks for each axis. + * @param {Object} extremes A mapping from seriesName -> [low, high] + * This fills in the valueRange and ticks fields in each entry of this.axes_. + */Dygraph.prototype.computeYAxisRanges_ = function(extremes){var isNullUndefinedOrNaN=function isNullUndefinedOrNaN(num){return isNaN(parseFloat(num));};var numAxes=this.attributes_.numAxes();var ypadCompat,span,series,ypad;var p_axis; // Compute extreme values, a span and tick marks for each axis. +for(var i=0;i < numAxes;i++) {var axis=this.axes_[i];var logscale=this.attributes_.getForAxis("logscale",i);var includeZero=this.attributes_.getForAxis("includeZero",i);var independentTicks=this.attributes_.getForAxis("independentTicks",i);series = this.attributes_.seriesForAxis(i); // Add some padding. This supports two Y padding operation modes: +// +// - backwards compatible (yRangePad not set): +// 10% padding for automatic Y ranges, but not for user-supplied +// ranges, and move a close-to-zero edge to zero, since drawing at the edge +// results in invisible lines. Unfortunately lines drawn at the edge of a +// user-supplied range will still be invisible. If logscale is +// set, add a variable amount of padding at the top but +// none at the bottom. +// +// - new-style (yRangePad set by the user): +// always add the specified Y padding. +// +ypadCompat = true;ypad = 0.1; // add 10% +var yRangePad=this.getNumericOption('yRangePad');if(yRangePad !== null){ypadCompat = false; // Convert pixel padding to ratio +ypad = yRangePad / this.plotter_.area.h;}if(series.length === 0){ // If no series are defined or visible then use a reasonable default +axis.extremeRange = [0,1];}else { // Calculate the extremes of extremes. +var minY=Infinity; // extremes[series[0]][0]; +var maxY=-Infinity; // extremes[series[0]][1]; +var extremeMinY,extremeMaxY;for(var j=0;j < series.length;j++) { // this skips invisible series +if(!extremes.hasOwnProperty(series[j]))continue; // Only use valid extremes to stop null data series' from corrupting the scale. +extremeMinY = extremes[series[j]][0];if(extremeMinY !== null){minY = Math.min(extremeMinY,minY);}extremeMaxY = extremes[series[j]][1];if(extremeMaxY !== null){maxY = Math.max(extremeMaxY,maxY);}} // Include zero if requested by the user. +if(includeZero && !logscale){if(minY > 0)minY = 0;if(maxY < 0)maxY = 0;} // Ensure we have a valid scale, otherwise default to [0, 1] for safety. +if(minY == Infinity)minY = 0;if(maxY == -Infinity)maxY = 1;span = maxY - minY; // special case: if we have no sense of scale, center on the sole value. +if(span === 0){if(maxY !== 0){span = Math.abs(maxY);}else { // ... and if the sole value is zero, use range 0-1. +maxY = 1;span = 1;}}var maxAxisY=maxY,minAxisY=minY;if(ypadCompat){if(logscale){maxAxisY = maxY + ypad * span;minAxisY = minY;}else {maxAxisY = maxY + ypad * span;minAxisY = minY - ypad * span; // Backwards-compatible behavior: Move the span to start or end at zero if it's +// close to zero. +if(minAxisY < 0 && minY >= 0)minAxisY = 0;if(maxAxisY > 0 && maxY <= 0)maxAxisY = 0;}}axis.extremeRange = [minAxisY,maxAxisY];}if(axis.valueRange){ // This is a user-set value range for this axis. +var y0=isNullUndefinedOrNaN(axis.valueRange[0])?axis.extremeRange[0]:axis.valueRange[0];var y1=isNullUndefinedOrNaN(axis.valueRange[1])?axis.extremeRange[1]:axis.valueRange[1];axis.computedValueRange = [y0,y1];}else {axis.computedValueRange = axis.extremeRange;}if(!ypadCompat){ // When using yRangePad, adjust the upper/lower bounds to add +// padding unless the user has zoomed/panned the Y axis range. +if(logscale){y0 = axis.computedValueRange[0];y1 = axis.computedValueRange[1];var y0pct=ypad / (2 * ypad - 1);var y1pct=(ypad - 1) / (2 * ypad - 1);axis.computedValueRange[0] = utils.logRangeFraction(y0,y1,y0pct);axis.computedValueRange[1] = utils.logRangeFraction(y0,y1,y1pct);}else {y0 = axis.computedValueRange[0];y1 = axis.computedValueRange[1];span = y1 - y0;axis.computedValueRange[0] = y0 - span * ypad;axis.computedValueRange[1] = y1 + span * ypad;}}if(independentTicks){axis.independentTicks = independentTicks;var opts=this.optionsViewForAxis_('y' + (i?'2':''));var ticker=opts('ticker');axis.ticks = ticker(axis.computedValueRange[0],axis.computedValueRange[1],this.plotter_.area.h,opts,this); // Define the first independent axis as primary axis. +if(!p_axis)p_axis = axis;}}if(p_axis === undefined){throw "Configuration Error: At least one axis has to have the \"independentTicks\" option activated.";} // Add ticks. By default, all axes inherit the tick positions of the +// primary axis. However, if an axis is specifically marked as having +// independent ticks, then that is permissible as well. +for(var i=0;i < numAxes;i++) {var axis=this.axes_[i];if(!axis.independentTicks){var opts=this.optionsViewForAxis_('y' + (i?'2':''));var ticker=opts('ticker');var p_ticks=p_axis.ticks;var p_scale=p_axis.computedValueRange[1] - p_axis.computedValueRange[0];var scale=axis.computedValueRange[1] - axis.computedValueRange[0];var tick_values=[];for(var k=0;k < p_ticks.length;k++) {var y_frac=(p_ticks[k].v - p_axis.computedValueRange[0]) / p_scale;var y_val=axis.computedValueRange[0] + y_frac * scale;tick_values.push(y_val);}axis.ticks = ticker(axis.computedValueRange[0],axis.computedValueRange[1],this.plotter_.area.h,opts,this,tick_values);}}}; /** + * Detects the type of the str (date or numeric) and sets the various + * formatting attributes in this.attrs_ based on this type. + * @param {string} str An x value. + * @private + */Dygraph.prototype.detectTypeFromString_ = function(str){var isDate=false;var dashPos=str.indexOf('-'); // could be 2006-01-01 _or_ 1.0e-2 +if(dashPos > 0 && str[dashPos - 1] != 'e' && str[dashPos - 1] != 'E' || str.indexOf('/') >= 0 || isNaN(parseFloat(str))){isDate = true;}else if(str.length == 8 && str > '19700101' && str < '20371231'){ // TODO(danvk): remove support for this format. +isDate = true;}this.setXAxisOptions_(isDate);};Dygraph.prototype.setXAxisOptions_ = function(isDate){if(isDate){this.attrs_.xValueParser = utils.dateParser;this.attrs_.axes.x.valueFormatter = utils.dateValueFormatter;this.attrs_.axes.x.ticker = DygraphTickers.dateTicker;this.attrs_.axes.x.axisLabelFormatter = utils.dateAxisLabelFormatter;}else { /** @private (shut up, jsdoc!) */this.attrs_.xValueParser = function(x){return parseFloat(x);}; // TODO(danvk): use Dygraph.numberValueFormatter here? +/** @private (shut up, jsdoc!) */this.attrs_.axes.x.valueFormatter = function(x){return x;};this.attrs_.axes.x.ticker = DygraphTickers.numericTicks;this.attrs_.axes.x.axisLabelFormatter = this.attrs_.axes.x.valueFormatter;}}; /** + * @private + * Parses a string in a special csv format. We expect a csv file where each + * line is a date point, and the first field in each line is the date string. + * We also expect that all remaining fields represent series. + * if the errorBars attribute is set, then interpret the fields as: + * date, series1, stddev1, series2, stddev2, ... + * @param {[Object]} data See above. + * + * @return [Object] An array with one entry for each row. These entries + * are an array of cells in that row. The first entry is the parsed x-value for + * the row. The second, third, etc. are the y-values. These can take on one of + * three forms, depending on the CSV and constructor parameters: + * 1. numeric value + * 2. [ value, stddev ] + * 3. [ low value, center value, high value ] + */Dygraph.prototype.parseCSV_ = function(data){var ret=[];var line_delimiter=utils.detectLineDelimiter(data);var lines=data.split(line_delimiter || "\n");var vals,j; // Use the default delimiter or fall back to a tab if that makes sense. +var delim=this.getStringOption('delimiter');if(lines[0].indexOf(delim) == -1 && lines[0].indexOf('\t') >= 0){delim = '\t';}var start=0;if(!('labels' in this.user_attrs_)){ // User hasn't explicitly set labels, so they're (presumably) in the CSV. +start = 1;this.attrs_.labels = lines[0].split(delim); // NOTE: _not_ user_attrs_. +this.attributes_.reparseSeries();}var line_no=0;var xParser;var defaultParserSet=false; // attempt to auto-detect x value type +var expectedCols=this.attr_("labels").length;var outOfOrder=false;for(var i=start;i < lines.length;i++) {var line=lines[i];line_no = i;if(line.length === 0)continue; // skip blank lines +if(line[0] == '#')continue; // skip comment lines +var inFields=line.split(delim);if(inFields.length < 2)continue;var fields=[];if(!defaultParserSet){this.detectTypeFromString_(inFields[0]);xParser = this.getFunctionOption("xValueParser");defaultParserSet = true;}fields[0] = xParser(inFields[0],this); // If fractions are expected, parse the numbers as "A/B" +if(this.fractions_){for(j = 1;j < inFields.length;j++) { // TODO(danvk): figure out an appropriate way to flag parse errors. +vals = inFields[j].split("/");if(vals.length != 2){console.error('Expected fractional "num/den" values in CSV data ' + "but found a value '" + inFields[j] + "' on line " + (1 + i) + " ('" + line + "') which is not of this form.");fields[j] = [0,0];}else {fields[j] = [utils.parseFloat_(vals[0],i,line),utils.parseFloat_(vals[1],i,line)];}}}else if(this.getBooleanOption("errorBars")){ // If there are error bars, values are (value, stddev) pairs +if(inFields.length % 2 != 1){console.error('Expected alternating (value, stdev.) pairs in CSV data ' + 'but line ' + (1 + i) + ' has an odd number of values (' + (inFields.length - 1) + "): '" + line + "'");}for(j = 1;j < inFields.length;j += 2) {fields[(j + 1) / 2] = [utils.parseFloat_(inFields[j],i,line),utils.parseFloat_(inFields[j + 1],i,line)];}}else if(this.getBooleanOption("customBars")){ // Bars are a low;center;high tuple +for(j = 1;j < inFields.length;j++) {var val=inFields[j];if(/^ *$/.test(val)){fields[j] = [null,null,null];}else {vals = val.split(";");if(vals.length == 3){fields[j] = [utils.parseFloat_(vals[0],i,line),utils.parseFloat_(vals[1],i,line),utils.parseFloat_(vals[2],i,line)];}else {console.warn('When using customBars, values must be either blank ' + 'or "low;center;high" tuples (got "' + val + '" on line ' + (1 + i));}}}}else { // Values are just numbers +for(j = 1;j < inFields.length;j++) {fields[j] = utils.parseFloat_(inFields[j],i,line);}}if(ret.length > 0 && fields[0] < ret[ret.length - 1][0]){outOfOrder = true;}if(fields.length != expectedCols){console.error("Number of columns in line " + i + " (" + fields.length + ") does not agree with number of labels (" + expectedCols + ") " + line);} // If the user specified the 'labels' option and none of the cells of the +// first row parsed correctly, then they probably double-specified the +// labels. We go with the values set in the option, discard this row and +// log a warning to the JS console. +if(i === 0 && this.attr_('labels')){var all_null=true;for(j = 0;all_null && j < fields.length;j++) {if(fields[j])all_null = false;}if(all_null){console.warn("The dygraphs 'labels' option is set, but the first row " + "of CSV data ('" + line + "') appears to also contain " + "labels. Will drop the CSV labels and use the option " + "labels.");continue;}}ret.push(fields);}if(outOfOrder){console.warn("CSV is out of order; order it correctly to speed loading.");ret.sort(function(a,b){return a[0] - b[0];});}return ret;}; // In native format, all values must be dates or numbers. +// This check isn't perfect but will catch most mistaken uses of strings. +function validateNativeFormat(data){var firstRow=data[0];var firstX=firstRow[0];if(typeof firstX !== 'number' && !utils.isDateLike(firstX)){throw new Error('Expected number or date but got ' + typeof firstX + ': ' + firstX + '.');}for(var i=1;i < firstRow.length;i++) {var val=firstRow[i];if(val === null || val === undefined)continue;if(typeof val === 'number')continue;if(utils.isArrayLike(val))continue; // e.g. error bars or custom bars. +throw new Error('Expected number or array but got ' + typeof val + ': ' + val + '.');}} /** + * The user has provided their data as a pre-packaged JS array. If the x values + * are numeric, this is the same as dygraphs' internal format. If the x values + * are dates, we need to convert them from Date objects to ms since epoch. + * @param {!Array} data + * @return {Object} data with numeric x values. + * @private + */Dygraph.prototype.parseArray_ = function(data){ // Peek at the first x value to see if it's numeric. +if(data.length === 0){console.error("Can't plot empty data set");return null;}if(data[0].length === 0){console.error("Data set cannot contain an empty row");return null;}validateNativeFormat(data);var i;if(this.attr_("labels") === null){console.warn("Using default labels. Set labels explicitly via 'labels' " + "in the options parameter");this.attrs_.labels = ["X"];for(i = 1;i < data[0].length;i++) {this.attrs_.labels.push("Y" + i); // Not user_attrs_. +}this.attributes_.reparseSeries();}else {var num_labels=this.attr_("labels");if(num_labels.length != data[0].length){console.error("Mismatch between number of labels (" + num_labels + ")" + " and number of columns in array (" + data[0].length + ")");return null;}}if(utils.isDateLike(data[0][0])){ // Some intelligent defaults for a date x-axis. +this.attrs_.axes.x.valueFormatter = utils.dateValueFormatter;this.attrs_.axes.x.ticker = DygraphTickers.dateTicker;this.attrs_.axes.x.axisLabelFormatter = utils.dateAxisLabelFormatter; // Assume they're all dates. +var parsedData=utils.clone(data);for(i = 0;i < data.length;i++) {if(parsedData[i].length === 0){console.error("Row " + (1 + i) + " of data is empty");return null;}if(parsedData[i][0] === null || typeof parsedData[i][0].getTime != 'function' || isNaN(parsedData[i][0].getTime())){console.error("x value in row " + (1 + i) + " is not a Date");return null;}parsedData[i][0] = parsedData[i][0].getTime();}return parsedData;}else { // Some intelligent defaults for a numeric x-axis. +/** @private (shut up, jsdoc!) */this.attrs_.axes.x.valueFormatter = function(x){return x;};this.attrs_.axes.x.ticker = DygraphTickers.numericTicks;this.attrs_.axes.x.axisLabelFormatter = utils.numberAxisLabelFormatter;return data;}}; /** + * Parses a DataTable object from gviz. + * The data is expected to have a first column that is either a date or a + * number. All subsequent columns must be numbers. If there is a clear mismatch + * between this.xValueParser_ and the type of the first column, it will be + * fixed. Fills out rawData_. + * @param {!google.visualization.DataTable} data See above. + * @private + */Dygraph.prototype.parseDataTable_ = function(data){var shortTextForAnnotationNum=function shortTextForAnnotationNum(num){ // converts [0-9]+ [A-Z][a-z]* +// example: 0=A, 1=B, 25=Z, 26=Aa, 27=Ab +// and continues like.. Ba Bb .. Za .. Zz..Aaa...Zzz Aaaa Zzzz +var shortText=String.fromCharCode(65 /* A */ + num % 26);num = Math.floor(num / 26);while(num > 0) {shortText = String.fromCharCode(65 /* A */ + (num - 1) % 26) + shortText.toLowerCase();num = Math.floor((num - 1) / 26);}return shortText;};var cols=data.getNumberOfColumns();var rows=data.getNumberOfRows();var indepType=data.getColumnType(0);if(indepType == 'date' || indepType == 'datetime'){this.attrs_.xValueParser = utils.dateParser;this.attrs_.axes.x.valueFormatter = utils.dateValueFormatter;this.attrs_.axes.x.ticker = DygraphTickers.dateTicker;this.attrs_.axes.x.axisLabelFormatter = utils.dateAxisLabelFormatter;}else if(indepType == 'number'){this.attrs_.xValueParser = function(x){return parseFloat(x);};this.attrs_.axes.x.valueFormatter = function(x){return x;};this.attrs_.axes.x.ticker = DygraphTickers.numericTicks;this.attrs_.axes.x.axisLabelFormatter = this.attrs_.axes.x.valueFormatter;}else {throw new Error("only 'date', 'datetime' and 'number' types are supported " + "for column 1 of DataTable input (Got '" + indepType + "')");} // Array of the column indices which contain data (and not annotations). +var colIdx=[];var annotationCols={}; // data index -> [annotation cols] +var hasAnnotations=false;var i,j;for(i = 1;i < cols;i++) {var type=data.getColumnType(i);if(type == 'number'){colIdx.push(i);}else if(type == 'string' && this.getBooleanOption('displayAnnotations')){ // This is OK -- it's an annotation column. +var dataIdx=colIdx[colIdx.length - 1];if(!annotationCols.hasOwnProperty(dataIdx)){annotationCols[dataIdx] = [i];}else {annotationCols[dataIdx].push(i);}hasAnnotations = true;}else {throw new Error("Only 'number' is supported as a dependent type with Gviz." + " 'string' is only supported if displayAnnotations is true");}} // Read column labels +// TODO(danvk): add support back for errorBars +var labels=[data.getColumnLabel(0)];for(i = 0;i < colIdx.length;i++) {labels.push(data.getColumnLabel(colIdx[i]));if(this.getBooleanOption("errorBars"))i += 1;}this.attrs_.labels = labels;cols = labels.length;var ret=[];var outOfOrder=false;var annotations=[];for(i = 0;i < rows;i++) {var row=[];if(typeof data.getValue(i,0) === 'undefined' || data.getValue(i,0) === null){console.warn("Ignoring row " + i + " of DataTable because of undefined or null first column.");continue;}if(indepType == 'date' || indepType == 'datetime'){row.push(data.getValue(i,0).getTime());}else {row.push(data.getValue(i,0));}if(!this.getBooleanOption("errorBars")){for(j = 0;j < colIdx.length;j++) {var col=colIdx[j];row.push(data.getValue(i,col));if(hasAnnotations && annotationCols.hasOwnProperty(col) && data.getValue(i,annotationCols[col][0]) !== null){var ann={};ann.series = data.getColumnLabel(col);ann.xval = row[0];ann.shortText = shortTextForAnnotationNum(annotations.length);ann.text = '';for(var k=0;k < annotationCols[col].length;k++) {if(k)ann.text += "\n";ann.text += data.getValue(i,annotationCols[col][k]);}annotations.push(ann);}} // Strip out infinities, which give dygraphs problems later on. +for(j = 0;j < row.length;j++) {if(!isFinite(row[j]))row[j] = null;}}else {for(j = 0;j < cols - 1;j++) {row.push([data.getValue(i,1 + 2 * j),data.getValue(i,2 + 2 * j)]);}}if(ret.length > 0 && row[0] < ret[ret.length - 1][0]){outOfOrder = true;}ret.push(row);}if(outOfOrder){console.warn("DataTable is out of order; order it correctly to speed loading.");ret.sort(function(a,b){return a[0] - b[0];});}this.rawData_ = ret;if(annotations.length > 0){this.setAnnotations(annotations,true);}this.attributes_.reparseSeries();}; /** + * Signals to plugins that the chart data has updated. + * This happens after the data has updated but before the chart has redrawn. + * @private + */Dygraph.prototype.cascadeDataDidUpdateEvent_ = function(){ // TODO(danvk): there are some issues checking xAxisRange() and using +// toDomCoords from handlers of this event. The visible range should be set +// when the chart is drawn, not derived from the data. +this.cascadeEvents_('dataDidUpdate',{});}; /** + * Get the CSV data. If it's in a function, call that function. If it's in a + * file, do an XMLHttpRequest to get it. + * @private + */Dygraph.prototype.start_ = function(){var data=this.file_; // Functions can return references of all other types. +if(typeof data == 'function'){data = data();}if(utils.isArrayLike(data)){this.rawData_ = this.parseArray_(data);this.cascadeDataDidUpdateEvent_();this.predraw_();}else if(typeof data == 'object' && typeof data.getColumnRange == 'function'){ // must be a DataTable from gviz. +this.parseDataTable_(data);this.cascadeDataDidUpdateEvent_();this.predraw_();}else if(typeof data == 'string'){ // Heuristic: a newline means it's CSV data. Otherwise it's an URL. +var line_delimiter=utils.detectLineDelimiter(data);if(line_delimiter){this.loadedEvent_(data);}else { // REMOVE_FOR_IE +var req;if(window.XMLHttpRequest){ // Firefox, Opera, IE7, and other browsers will use the native object +req = new XMLHttpRequest();}else { // IE 5 and 6 will use the ActiveX control +req = new ActiveXObject("Microsoft.XMLHTTP");}var caller=this;req.onreadystatechange = function(){if(req.readyState == 4){if(req.status === 200 || // Normal http +req.status === 0){ // Chrome w/ --allow-file-access-from-files +caller.loadedEvent_(req.responseText);}}};req.open("GET",data,true);req.send(null);}}else {console.error("Unknown data format: " + typeof data);}}; /** + * Changes various properties of the graph. These can include: + *
    + *
  • file: changes the source data for the graph
  • + *
  • errorBars: changes whether the data contains stddev
  • + *
+ * + * There's a huge variety of options that can be passed to this method. For a + * full list, see http://dygraphs.com/options.html. + * + * @param {Object} input_attrs The new properties and values + * @param {boolean} block_redraw Usually the chart is redrawn after every + * call to updateOptions(). If you know better, you can pass true to + * explicitly block the redraw. This can be useful for chaining + * updateOptions() calls, avoiding the occasional infinite loop and + * preventing redraws when it's not necessary (e.g. when updating a + * callback). + */Dygraph.prototype.updateOptions = function(input_attrs,block_redraw){if(typeof block_redraw == 'undefined')block_redraw = false; // copyUserAttrs_ drops the "file" parameter as a convenience to us. +var file=input_attrs.file;var attrs=Dygraph.copyUserAttrs_(input_attrs); // TODO(danvk): this is a mess. Move these options into attr_. +if('rollPeriod' in attrs){this.rollPeriod_ = attrs.rollPeriod;}if('dateWindow' in attrs){this.dateWindow_ = attrs.dateWindow;} // TODO(danvk): validate per-series options. +// Supported: +// strokeWidth +// pointSize +// drawPoints +// highlightCircleSize +// Check if this set options will require new points. +var requiresNewPoints=utils.isPixelChangingOptionList(this.attr_("labels"),attrs);utils.updateDeep(this.user_attrs_,attrs);this.attributes_.reparseSeries();if(file){ // This event indicates that the data is about to change, but hasn't yet. +// TODO(danvk): support cancellation of the update via this event. +this.cascadeEvents_('dataWillUpdate',{});this.file_ = file;if(!block_redraw)this.start_();}else {if(!block_redraw){if(requiresNewPoints){this.predraw_();}else {this.renderGraph_(false);}}}}; /** + * Make a copy of input attributes, removing file as a convenience. + * @private + */Dygraph.copyUserAttrs_ = function(attrs){var my_attrs={};for(var k in attrs) {if(!attrs.hasOwnProperty(k))continue;if(k == 'file')continue;if(attrs.hasOwnProperty(k))my_attrs[k] = attrs[k];}return my_attrs;}; /** + * Resizes the dygraph. If no parameters are specified, resizes to fill the + * containing div (which has presumably changed size since the dygraph was + * instantiated. If the width/height are specified, the div will be resized. + * + * This is far more efficient than destroying and re-instantiating a + * Dygraph, since it doesn't have to reparse the underlying data. + * + * @param {number} width Width (in pixels) + * @param {number} height Height (in pixels) + */Dygraph.prototype.resize = function(width,height){if(this.resize_lock){return;}this.resize_lock = true;if(width === null != (height === null)){console.warn("Dygraph.resize() should be called with zero parameters or " + "two non-NULL parameters. Pretending it was zero.");width = height = null;}var old_width=this.width_;var old_height=this.height_;if(width){this.maindiv_.style.width = width + "px";this.maindiv_.style.height = height + "px";this.width_ = width;this.height_ = height;}else {this.width_ = this.maindiv_.clientWidth;this.height_ = this.maindiv_.clientHeight;}if(old_width != this.width_ || old_height != this.height_){ // Resizing a canvas erases it, even when the size doesn't change, so +// any resize needs to be followed by a redraw. +this.resizeElements_();this.predraw_();}this.resize_lock = false;}; /** + * Adjusts the number of points in the rolling average. Updates the graph to + * reflect the new averaging period. + * @param {number} length Number of points over which to average the data. + */Dygraph.prototype.adjustRoll = function(length){this.rollPeriod_ = length;this.predraw_();}; /** + * Returns a boolean array of visibility statuses. + */Dygraph.prototype.visibility = function(){ // Do lazy-initialization, so that this happens after we know the number of +// data series. +if(!this.getOption("visibility")){this.attrs_.visibility = [];} // TODO(danvk): it looks like this could go into an infinite loop w/ user_attrs. +while(this.getOption("visibility").length < this.numColumns() - 1) {this.attrs_.visibility.push(true);}return this.getOption("visibility");}; /** + * Changes the visibility of one or more series. + * + * @param {number|number[]|object} num the series index or an array of series indices + * or a boolean array of visibility states by index + * or an object mapping series numbers, as keys, to + * visibility state (boolean values) + * @param {boolean} value the visibility state expressed as a boolean + */Dygraph.prototype.setVisibility = function(num,value){var x=this.visibility();var numIsObject=false;if(!Array.isArray(num)){if(num !== null && typeof num === 'object'){numIsObject = true;}else {num = [num];}}if(numIsObject){for(var i in num) {if(num.hasOwnProperty(i)){if(i < 0 || i >= x.length){console.warn("Invalid series number in setVisibility: " + i);}else {x[i] = num[i];}}}}else {for(var i=0;i < num.length;i++) {if(typeof num[i] === 'boolean'){if(i >= x.length){console.warn("Invalid series number in setVisibility: " + i);}else {x[i] = num[i];}}else {if(num[i] < 0 || num[i] >= x.length){console.warn("Invalid series number in setVisibility: " + num[i]);}else {x[num[i]] = value;}}}}this.predraw_();}; /** + * How large of an area will the dygraph render itself in? + * This is used for testing. + * @return A {width: w, height: h} object. + * @private + */Dygraph.prototype.size = function(){return {width:this.width_,height:this.height_};}; /** + * Update the list of annotations and redraw the chart. + * See dygraphs.com/annotations.html for more info on how to use annotations. + * @param ann {Array} An array of annotation objects. + * @param suppressDraw {Boolean} Set to "true" to block chart redraw (optional). + */Dygraph.prototype.setAnnotations = function(ann,suppressDraw){ // Only add the annotation CSS rule once we know it will be used. +this.annotations_ = ann;if(!this.layout_){console.warn("Tried to setAnnotations before dygraph was ready. " + "Try setting them in a ready() block. See " + "dygraphs.com/tests/annotation.html");return;}this.layout_.setAnnotations(this.annotations_);if(!suppressDraw){this.predraw_();}}; /** + * Return the list of annotations. + */Dygraph.prototype.annotations = function(){return this.annotations_;}; /** + * Get the list of label names for this graph. The first column is the + * x-axis, so the data series names start at index 1. + * + * Returns null when labels have not yet been defined. + */Dygraph.prototype.getLabels = function(){var labels=this.attr_("labels");return labels?labels.slice():null;}; /** + * Get the index of a series (column) given its name. The first column is the + * x-axis, so the data series start with index 1. + */Dygraph.prototype.indexFromSetName = function(name){return this.setIndexByName_[name];}; /** + * Find the row number corresponding to the given x-value. + * Returns null if there is no such x-value in the data. + * If there are multiple rows with the same x-value, this will return the + * first one. + * @param {number} xVal The x-value to look for (e.g. millis since epoch). + * @return {?number} The row number, which you can pass to getValue(), or null. + */Dygraph.prototype.getRowForX = function(xVal){var low=0,high=this.numRows() - 1;while(low <= high) {var idx=high + low >> 1;var x=this.getValue(idx,0);if(x < xVal){low = idx + 1;}else if(x > xVal){high = idx - 1;}else if(low != idx){ // equal, but there may be an earlier match. +high = idx;}else {return idx;}}return null;}; /** + * Trigger a callback when the dygraph has drawn itself and is ready to be + * manipulated. This is primarily useful when dygraphs has to do an XHR for the + * data (i.e. a URL is passed as the data source) and the chart is drawn + * asynchronously. If the chart has already drawn, the callback will fire + * immediately. + * + * This is a good place to call setAnnotation(). + * + * @param {function(!Dygraph)} callback The callback to trigger when the chart + * is ready. + */Dygraph.prototype.ready = function(callback){if(this.is_initial_draw_){this.readyFns_.push(callback);}else {callback.call(this,this);}}; /** + * Add an event handler. This event handler is kept until the graph is + * destroyed with a call to graph.destroy(). + * + * @param {!Node} elem The element to add the event to. + * @param {string} type The type of the event, e.g. 'click' or 'mousemove'. + * @param {function(Event):(boolean|undefined)} fn The function to call + * on the event. The function takes one parameter: the event object. + * @private + */Dygraph.prototype.addAndTrackEvent = function(elem,type,fn){utils.addEvent(elem,type,fn);this.registeredEvents_.push({elem:elem,type:type,fn:fn});};Dygraph.prototype.removeTrackedEvents_ = function(){if(this.registeredEvents_){for(var idx=0;idx < this.registeredEvents_.length;idx++) {var reg=this.registeredEvents_[idx];utils.removeEvent(reg.elem,reg.type,reg.fn);}}this.registeredEvents_ = [];}; // Installed plugins, in order of precedence (most-general to most-specific). +Dygraph.PLUGINS = [_pluginsLegend2['default'],_pluginsAxes2['default'],_pluginsRangeSelector2['default'], // Has to be before ChartLabels so that its callbacks are called after ChartLabels' callbacks. +_pluginsChartLabels2['default'],_pluginsAnnotations2['default'],_pluginsGrid2['default']]; // There are many symbols which have historically been available through the +// Dygraph class. These are exported here for backwards compatibility. +Dygraph.GVizChart = _dygraphGviz2['default'];Dygraph.DASHED_LINE = utils.DASHED_LINE;Dygraph.DOT_DASH_LINE = utils.DOT_DASH_LINE;Dygraph.dateAxisLabelFormatter = utils.dateAxisLabelFormatter;Dygraph.toRGB_ = utils.toRGB_;Dygraph.findPos = utils.findPos;Dygraph.pageX = utils.pageX;Dygraph.pageY = utils.pageY;Dygraph.dateString_ = utils.dateString_;Dygraph.defaultInteractionModel = _dygraphInteractionModel2['default'].defaultModel;Dygraph.nonInteractiveModel = Dygraph.nonInteractiveModel_ = _dygraphInteractionModel2['default'].nonInteractiveModel_;Dygraph.Circles = utils.Circles;Dygraph.Plugins = {Legend:_pluginsLegend2['default'],Axes:_pluginsAxes2['default'],Annotations:_pluginsAnnotations2['default'],ChartLabels:_pluginsChartLabels2['default'],Grid:_pluginsGrid2['default'],RangeSelector:_pluginsRangeSelector2['default']};Dygraph.DataHandlers = {DefaultHandler:_datahandlerDefault2['default'],BarsHandler:_datahandlerBars2['default'],CustomBarsHandler:_datahandlerBarsCustom2['default'],DefaultFractionHandler:_datahandlerDefaultFractions2['default'],ErrorBarsHandler:_datahandlerBarsError2['default'],FractionsBarsHandler:_datahandlerBarsFractions2['default']};Dygraph.startPan = _dygraphInteractionModel2['default'].startPan;Dygraph.startZoom = _dygraphInteractionModel2['default'].startZoom;Dygraph.movePan = _dygraphInteractionModel2['default'].movePan;Dygraph.moveZoom = _dygraphInteractionModel2['default'].moveZoom;Dygraph.endPan = _dygraphInteractionModel2['default'].endPan;Dygraph.endZoom = _dygraphInteractionModel2['default'].endZoom;Dygraph.numericLinearTicks = DygraphTickers.numericLinearTicks;Dygraph.numericTicks = DygraphTickers.numericTicks;Dygraph.dateTicker = DygraphTickers.dateTicker;Dygraph.Granularity = DygraphTickers.Granularity;Dygraph.getDateAxis = DygraphTickers.getDateAxis;Dygraph.floatFormat = utils.floatFormat;exports['default'] = Dygraph;module.exports = exports['default']; + +}).call(this,require('_process')) + +},{"./datahandler/bars":5,"./datahandler/bars-custom":2,"./datahandler/bars-error":3,"./datahandler/bars-fractions":4,"./datahandler/default":8,"./datahandler/default-fractions":7,"./dygraph-canvas":9,"./dygraph-default-attrs":10,"./dygraph-gviz":11,"./dygraph-interaction-model":12,"./dygraph-layout":13,"./dygraph-options":15,"./dygraph-options-reference":14,"./dygraph-tickers":16,"./dygraph-utils":17,"./iframe-tarp":19,"./plugins/annotations":20,"./plugins/axes":21,"./plugins/chart-labels":22,"./plugins/grid":23,"./plugins/legend":24,"./plugins/range-selector":25,"_process":1}],19:[function(require,module,exports){ +/** + * To create a "drag" interaction, you typically register a mousedown event + * handler on the element where the drag begins. In that handler, you register a + * mouseup handler on the window to determine when the mouse is released, + * wherever that release happens. This works well, except when the user releases + * the mouse over an off-domain iframe. In that case, the mouseup event is + * handled by the iframe and never bubbles up to the window handler. + * + * To deal with this issue, we cover iframes with high z-index divs to make sure + * they don't capture mouseup. + * + * Usage: + * element.addEventListener('mousedown', function() { + * var tarper = new IFrameTarp(); + * tarper.cover(); + * var mouseUpHandler = function() { + * ... + * window.removeEventListener(mouseUpHandler); + * tarper.uncover(); + * }; + * window.addEventListener('mouseup', mouseUpHandler); + * }; + * + * @constructor + */ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj["default"] = obj; return newObj; } } + +var _dygraphUtils = require('./dygraph-utils'); + +var utils = _interopRequireWildcard(_dygraphUtils); + +function IFrameTarp() { + /** @type {Array.} */ + this.tarps = []; +}; + +/** + * Find all the iframes in the document and cover them with high z-index + * transparent divs. + */ +IFrameTarp.prototype.cover = function () { + var iframes = document.getElementsByTagName("iframe"); + for (var i = 0; i < iframes.length; i++) { + var iframe = iframes[i]; + var pos = utils.findPos(iframe), + x = pos.x, + y = pos.y, + width = iframe.offsetWidth, + height = iframe.offsetHeight; + + var div = document.createElement("div"); + div.style.position = "absolute"; + div.style.left = x + 'px'; + div.style.top = y + 'px'; + div.style.width = width + 'px'; + div.style.height = height + 'px'; + div.style.zIndex = 999; + document.body.appendChild(div); + this.tarps.push(div); + } +}; + +/** + * Remove all the iframe covers. You should call this in a mouseup handler. + */ +IFrameTarp.prototype.uncover = function () { + for (var i = 0; i < this.tarps.length; i++) { + this.tarps[i].parentNode.removeChild(this.tarps[i]); + } + this.tarps = []; +}; + +exports["default"] = IFrameTarp; +module.exports = exports["default"]; + +},{"./dygraph-utils":17}],20:[function(require,module,exports){ +/** + * @license + * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/*global Dygraph:false */ + +"use strict"; + +/** +Current bits of jankiness: +- Uses dygraph.layout_ to get the parsed annotations. +- Uses dygraph.plotter_.area + +It would be nice if the plugin didn't require so much special support inside +the core dygraphs classes, but annotations involve quite a bit of parsing and +layout. + +TODO(danvk): cache DOM elements. +*/ + +Object.defineProperty(exports, "__esModule", { + value: true +}); +var annotations = function annotations() { + this.annotations_ = []; +}; + +annotations.prototype.toString = function () { + return "Annotations Plugin"; +}; + +annotations.prototype.activate = function (g) { + return { + clearChart: this.clearChart, + didDrawChart: this.didDrawChart + }; +}; + +annotations.prototype.detachLabels = function () { + for (var i = 0; i < this.annotations_.length; i++) { + var a = this.annotations_[i]; + if (a.parentNode) a.parentNode.removeChild(a); + this.annotations_[i] = null; + } + this.annotations_ = []; +}; + +annotations.prototype.clearChart = function (e) { + this.detachLabels(); +}; + +annotations.prototype.didDrawChart = function (e) { + var g = e.dygraph; + + // Early out in the (common) case of zero annotations. + var points = g.layout_.annotated_points; + if (!points || points.length === 0) return; + + var containerDiv = e.canvas.parentNode; + + var bindEvt = function bindEvt(eventName, classEventName, pt) { + return function (annotation_event) { + var a = pt.annotation; + if (a.hasOwnProperty(eventName)) { + a[eventName](a, pt, g, annotation_event); + } else if (g.getOption(classEventName)) { + g.getOption(classEventName)(a, pt, g, annotation_event); + } + }; + }; + + // Add the annotations one-by-one. + var area = e.dygraph.getArea(); + + // x-coord to sum of previous annotation's heights (used for stacking). + var xToUsedHeight = {}; + + for (var i = 0; i < points.length; i++) { + var p = points[i]; + if (p.canvasx < area.x || p.canvasx > area.x + area.w || p.canvasy < area.y || p.canvasy > area.y + area.h) { + continue; + } + + var a = p.annotation; + var tick_height = 6; + if (a.hasOwnProperty("tickHeight")) { + tick_height = a.tickHeight; + } + + // TODO: deprecate axisLabelFontSize in favor of CSS + var div = document.createElement("div"); + div.style['fontSize'] = g.getOption('axisLabelFontSize') + "px"; + var className = 'dygraph-annotation'; + if (!a.hasOwnProperty('icon')) { + // camelCase class names are deprecated. + className += ' dygraphDefaultAnnotation dygraph-default-annotation'; + } + if (a.hasOwnProperty('cssClass')) { + className += " " + a.cssClass; + } + div.className = className; + + var width = a.hasOwnProperty('width') ? a.width : 16; + var height = a.hasOwnProperty('height') ? a.height : 16; + if (a.hasOwnProperty('icon')) { + var img = document.createElement("img"); + img.src = a.icon; + img.width = width; + img.height = height; + div.appendChild(img); + } else if (p.annotation.hasOwnProperty('shortText')) { + div.appendChild(document.createTextNode(p.annotation.shortText)); + } + var left = p.canvasx - width / 2; + div.style.left = left + "px"; + var divTop = 0; + if (a.attachAtBottom) { + var y = area.y + area.h - height - tick_height; + if (xToUsedHeight[left]) { + y -= xToUsedHeight[left]; + } else { + xToUsedHeight[left] = 0; + } + xToUsedHeight[left] += tick_height + height; + divTop = y; + } else { + divTop = p.canvasy - height - tick_height; + } + div.style.top = divTop + "px"; + div.style.width = width + "px"; + div.style.height = height + "px"; + div.title = p.annotation.text; + div.style.color = g.colorsMap_[p.name]; + div.style.borderColor = g.colorsMap_[p.name]; + a.div = div; + + g.addAndTrackEvent(div, 'click', bindEvt('clickHandler', 'annotationClickHandler', p, this)); + g.addAndTrackEvent(div, 'mouseover', bindEvt('mouseOverHandler', 'annotationMouseOverHandler', p, this)); + g.addAndTrackEvent(div, 'mouseout', bindEvt('mouseOutHandler', 'annotationMouseOutHandler', p, this)); + g.addAndTrackEvent(div, 'dblclick', bindEvt('dblClickHandler', 'annotationDblClickHandler', p, this)); + + containerDiv.appendChild(div); + this.annotations_.push(div); + + var ctx = e.drawingContext; + ctx.save(); + ctx.strokeStyle = a.hasOwnProperty('tickColor') ? a.tickColor : g.colorsMap_[p.name]; + ctx.lineWidth = a.hasOwnProperty('tickWidth') ? a.tickWidth : g.getOption('strokeWidth'); + ctx.beginPath(); + if (!a.attachAtBottom) { + ctx.moveTo(p.canvasx, p.canvasy); + ctx.lineTo(p.canvasx, p.canvasy - 2 - tick_height); + } else { + var y = divTop + height; + ctx.moveTo(p.canvasx, y); + ctx.lineTo(p.canvasx, y + tick_height); + } + ctx.closePath(); + ctx.stroke(); + ctx.restore(); + } +}; + +annotations.prototype.destroy = function () { + this.detachLabels(); +}; + +exports["default"] = annotations; +module.exports = exports["default"]; + +},{}],21:[function(require,module,exports){ +/** + * @license + * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/*global Dygraph:false */ + +'use strict'; + +/* +Bits of jankiness: +- Direct layout access +- Direct area access +- Should include calculation of ticks, not just the drawing. + +Options left to make axis-friendly. + ('drawAxesAtZero') + ('xAxisHeight') +*/ + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +var _dygraphUtils = require('../dygraph-utils'); + +var utils = _interopRequireWildcard(_dygraphUtils); + +/** + * Draws the axes. This includes the labels on the x- and y-axes, as well + * as the tick marks on the axes. + * It does _not_ draw the grid lines which span the entire chart. + */ +var axes = function axes() { + this.xlabels_ = []; + this.ylabels_ = []; +}; + +axes.prototype.toString = function () { + return 'Axes Plugin'; +}; + +axes.prototype.activate = function (g) { + return { + layout: this.layout, + clearChart: this.clearChart, + willDrawChart: this.willDrawChart + }; +}; + +axes.prototype.layout = function (e) { + var g = e.dygraph; + + if (g.getOptionForAxis('drawAxis', 'y')) { + var w = g.getOptionForAxis('axisLabelWidth', 'y') + 2 * g.getOptionForAxis('axisTickSize', 'y'); + e.reserveSpaceLeft(w); + } + + if (g.getOptionForAxis('drawAxis', 'x')) { + var h; + // NOTE: I think this is probably broken now, since g.getOption() now + // hits the dictionary. (That is, g.getOption('xAxisHeight') now always + // has a value.) + if (g.getOption('xAxisHeight')) { + h = g.getOption('xAxisHeight'); + } else { + h = g.getOptionForAxis('axisLabelFontSize', 'x') + 2 * g.getOptionForAxis('axisTickSize', 'x'); + } + e.reserveSpaceBottom(h); + } + + if (g.numAxes() == 2) { + if (g.getOptionForAxis('drawAxis', 'y2')) { + var w = g.getOptionForAxis('axisLabelWidth', 'y2') + 2 * g.getOptionForAxis('axisTickSize', 'y2'); + e.reserveSpaceRight(w); + } + } else if (g.numAxes() > 2) { + g.error('Only two y-axes are supported at this time. (Trying ' + 'to use ' + g.numAxes() + ')'); + } +}; + +axes.prototype.detachLabels = function () { + function removeArray(ary) { + for (var i = 0; i < ary.length; i++) { + var el = ary[i]; + if (el.parentNode) el.parentNode.removeChild(el); + } + } + + removeArray(this.xlabels_); + removeArray(this.ylabels_); + this.xlabels_ = []; + this.ylabels_ = []; +}; + +axes.prototype.clearChart = function (e) { + this.detachLabels(); +}; + +axes.prototype.willDrawChart = function (e) { + var _this = this; + + var g = e.dygraph; + + if (!g.getOptionForAxis('drawAxis', 'x') && !g.getOptionForAxis('drawAxis', 'y') && !g.getOptionForAxis('drawAxis', 'y2')) { + return; + } + + // Round pixels to half-integer boundaries for crisper drawing. + function halfUp(x) { + return Math.round(x) + 0.5; + } + function halfDown(y) { + return Math.round(y) - 0.5; + } + + var context = e.drawingContext; + var containerDiv = e.canvas.parentNode; + var canvasWidth = g.width_; // e.canvas.width is affected by pixel ratio. + var canvasHeight = g.height_; + + var label, x, y, tick, i; + + var makeLabelStyle = function makeLabelStyle(axis) { + return { + position: 'absolute', + fontSize: g.getOptionForAxis('axisLabelFontSize', axis) + 'px', + width: g.getOptionForAxis('axisLabelWidth', axis) + 'px' + }; + }; + + var labelStyles = { + x: makeLabelStyle('x'), + y: makeLabelStyle('y'), + y2: makeLabelStyle('y2') + }; + + var makeDiv = function makeDiv(txt, axis, prec_axis) { + /* + * This seems to be called with the following three sets of axis/prec_axis: + * x: undefined + * y: y1 + * y: y2 + */ + var div = document.createElement('div'); + var labelStyle = labelStyles[prec_axis == 'y2' ? 'y2' : axis]; + utils.update(div.style, labelStyle); + // TODO: combine outer & inner divs + var inner_div = document.createElement('div'); + inner_div.className = 'dygraph-axis-label' + ' dygraph-axis-label-' + axis + (prec_axis ? ' dygraph-axis-label-' + prec_axis : ''); + inner_div.innerHTML = txt; + div.appendChild(inner_div); + return div; + }; + + // axis lines + context.save(); + + var layout = g.layout_; + var area = e.dygraph.plotter_.area; + + // Helper for repeated axis-option accesses. + var makeOptionGetter = function makeOptionGetter(axis) { + return function (option) { + return g.getOptionForAxis(option, axis); + }; + }; + + if (g.getOptionForAxis('drawAxis', 'y')) { + if (layout.yticks && layout.yticks.length > 0) { + var num_axes = g.numAxes(); + var getOptions = [makeOptionGetter('y'), makeOptionGetter('y2')]; + layout.yticks.forEach(function (tick) { + if (tick.label === undefined) return; // this tick only has a grid line. + x = area.x; + var sgn = 1; + var prec_axis = 'y1'; + var getAxisOption = getOptions[0]; + if (tick.axis == 1) { + // right-side y-axis + x = area.x + area.w; + sgn = -1; + prec_axis = 'y2'; + getAxisOption = getOptions[1]; + } + var fontSize = getAxisOption('axisLabelFontSize'); + y = area.y + tick.pos * area.h; + + /* Tick marks are currently clipped, so don't bother drawing them. + context.beginPath(); + context.moveTo(halfUp(x), halfDown(y)); + context.lineTo(halfUp(x - sgn * this.attr_('axisTickSize')), halfDown(y)); + context.closePath(); + context.stroke(); + */ + + label = makeDiv(tick.label, 'y', num_axes == 2 ? prec_axis : null); + var top = y - fontSize / 2; + if (top < 0) top = 0; + + if (top + fontSize + 3 > canvasHeight) { + label.style.bottom = '0'; + } else { + label.style.top = top + 'px'; + } + // TODO: replace these with css classes? + if (tick.axis === 0) { + label.style.left = area.x - getAxisOption('axisLabelWidth') - getAxisOption('axisTickSize') + 'px'; + label.style.textAlign = 'right'; + } else if (tick.axis == 1) { + label.style.left = area.x + area.w + getAxisOption('axisTickSize') + 'px'; + label.style.textAlign = 'left'; + } + label.style.width = getAxisOption('axisLabelWidth') + 'px'; + containerDiv.appendChild(label); + _this.ylabels_.push(label); + }); + + // The lowest tick on the y-axis often overlaps with the leftmost + // tick on the x-axis. Shift the bottom tick up a little bit to + // compensate if necessary. + var bottomTick = this.ylabels_[0]; + // Interested in the y2 axis also? + var fontSize = g.getOptionForAxis('axisLabelFontSize', 'y'); + var bottom = parseInt(bottomTick.style.top, 10) + fontSize; + if (bottom > canvasHeight - fontSize) { + bottomTick.style.top = parseInt(bottomTick.style.top, 10) - fontSize / 2 + 'px'; + } + } + + // draw a vertical line on the left to separate the chart from the labels. + var axisX; + if (g.getOption('drawAxesAtZero')) { + var r = g.toPercentXCoord(0); + if (r > 1 || r < 0 || isNaN(r)) r = 0; + axisX = halfUp(area.x + r * area.w); + } else { + axisX = halfUp(area.x); + } + + context.strokeStyle = g.getOptionForAxis('axisLineColor', 'y'); + context.lineWidth = g.getOptionForAxis('axisLineWidth', 'y'); + + context.beginPath(); + context.moveTo(axisX, halfDown(area.y)); + context.lineTo(axisX, halfDown(area.y + area.h)); + context.closePath(); + context.stroke(); + + // if there's a secondary y-axis, draw a vertical line for that, too. + if (g.numAxes() == 2) { + context.strokeStyle = g.getOptionForAxis('axisLineColor', 'y2'); + context.lineWidth = g.getOptionForAxis('axisLineWidth', 'y2'); + context.beginPath(); + context.moveTo(halfDown(area.x + area.w), halfDown(area.y)); + context.lineTo(halfDown(area.x + area.w), halfDown(area.y + area.h)); + context.closePath(); + context.stroke(); + } + } + + if (g.getOptionForAxis('drawAxis', 'x')) { + if (layout.xticks) { + var getAxisOption = makeOptionGetter('x'); + layout.xticks.forEach(function (tick) { + if (tick.label === undefined) return; // this tick only has a grid line. + x = area.x + tick.pos * area.w; + y = area.y + area.h; + + /* Tick marks are currently clipped, so don't bother drawing them. + context.beginPath(); + context.moveTo(halfUp(x), halfDown(y)); + context.lineTo(halfUp(x), halfDown(y + this.attr_('axisTickSize'))); + context.closePath(); + context.stroke(); + */ + + label = makeDiv(tick.label, 'x'); + label.style.textAlign = 'center'; + label.style.top = y + getAxisOption('axisTickSize') + 'px'; + + var left = x - getAxisOption('axisLabelWidth') / 2; + if (left + getAxisOption('axisLabelWidth') > canvasWidth) { + left = canvasWidth - getAxisOption('axisLabelWidth'); + label.style.textAlign = 'right'; + } + if (left < 0) { + left = 0; + label.style.textAlign = 'left'; + } + + label.style.left = left + 'px'; + label.style.width = getAxisOption('axisLabelWidth') + 'px'; + containerDiv.appendChild(label); + _this.xlabels_.push(label); + }); + } + + context.strokeStyle = g.getOptionForAxis('axisLineColor', 'x'); + context.lineWidth = g.getOptionForAxis('axisLineWidth', 'x'); + context.beginPath(); + var axisY; + if (g.getOption('drawAxesAtZero')) { + var r = g.toPercentYCoord(0, 0); + if (r > 1 || r < 0) r = 1; + axisY = halfDown(area.y + r * area.h); + } else { + axisY = halfDown(area.y + area.h); + } + context.moveTo(halfUp(area.x), axisY); + context.lineTo(halfUp(area.x + area.w), axisY); + context.closePath(); + context.stroke(); + } + + context.restore(); +}; + +exports['default'] = axes; +module.exports = exports['default']; + +},{"../dygraph-utils":17}],22:[function(require,module,exports){ +/** + * @license + * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ +/*global Dygraph:false */ + +"use strict"; + +// TODO(danvk): move chart label options out of dygraphs and into the plugin. +// TODO(danvk): only tear down & rebuild the DIVs when it's necessary. + +Object.defineProperty(exports, "__esModule", { + value: true +}); +var chart_labels = function chart_labels() { + this.title_div_ = null; + this.xlabel_div_ = null; + this.ylabel_div_ = null; + this.y2label_div_ = null; +}; + +chart_labels.prototype.toString = function () { + return "ChartLabels Plugin"; +}; + +chart_labels.prototype.activate = function (g) { + return { + layout: this.layout, + // clearChart: this.clearChart, + didDrawChart: this.didDrawChart + }; +}; + +// QUESTION: should there be a plugin-utils.js? +var createDivInRect = function createDivInRect(r) { + var div = document.createElement('div'); + div.style.position = 'absolute'; + div.style.left = r.x + 'px'; + div.style.top = r.y + 'px'; + div.style.width = r.w + 'px'; + div.style.height = r.h + 'px'; + return div; +}; + +// Detach and null out any existing nodes. +chart_labels.prototype.detachLabels_ = function () { + var els = [this.title_div_, this.xlabel_div_, this.ylabel_div_, this.y2label_div_]; + for (var i = 0; i < els.length; i++) { + var el = els[i]; + if (!el) continue; + if (el.parentNode) el.parentNode.removeChild(el); + } + + this.title_div_ = null; + this.xlabel_div_ = null; + this.ylabel_div_ = null; + this.y2label_div_ = null; +}; + +var createRotatedDiv = function createRotatedDiv(g, box, axis, classes, html) { + // TODO(danvk): is this outer div actually necessary? + var div = document.createElement("div"); + div.style.position = 'absolute'; + if (axis == 1) { + // NOTE: this is cheating. Should be positioned relative to the box. + div.style.left = '0px'; + } else { + div.style.left = box.x + 'px'; + } + div.style.top = box.y + 'px'; + div.style.width = box.w + 'px'; + div.style.height = box.h + 'px'; + div.style.fontSize = g.getOption('yLabelWidth') - 2 + 'px'; + + var inner_div = document.createElement("div"); + inner_div.style.position = 'absolute'; + inner_div.style.width = box.h + 'px'; + inner_div.style.height = box.w + 'px'; + inner_div.style.top = box.h / 2 - box.w / 2 + 'px'; + inner_div.style.left = box.w / 2 - box.h / 2 + 'px'; + // TODO: combine inner_div and class_div. + inner_div.className = 'dygraph-label-rotate-' + (axis == 1 ? 'right' : 'left'); + + var class_div = document.createElement("div"); + class_div.className = classes; + class_div.innerHTML = html; + + inner_div.appendChild(class_div); + div.appendChild(inner_div); + return div; +}; + +chart_labels.prototype.layout = function (e) { + this.detachLabels_(); + + var g = e.dygraph; + var div = e.chart_div; + if (g.getOption('title')) { + // QUESTION: should this return an absolutely-positioned div instead? + var title_rect = e.reserveSpaceTop(g.getOption('titleHeight')); + this.title_div_ = createDivInRect(title_rect); + this.title_div_.style.fontSize = g.getOption('titleHeight') - 8 + 'px'; + + var class_div = document.createElement("div"); + class_div.className = 'dygraph-label dygraph-title'; + class_div.innerHTML = g.getOption('title'); + this.title_div_.appendChild(class_div); + div.appendChild(this.title_div_); + } + + if (g.getOption('xlabel')) { + var x_rect = e.reserveSpaceBottom(g.getOption('xLabelHeight')); + this.xlabel_div_ = createDivInRect(x_rect); + this.xlabel_div_.style.fontSize = g.getOption('xLabelHeight') - 2 + 'px'; + + var class_div = document.createElement("div"); + class_div.className = 'dygraph-label dygraph-xlabel'; + class_div.innerHTML = g.getOption('xlabel'); + this.xlabel_div_.appendChild(class_div); + div.appendChild(this.xlabel_div_); + } + + if (g.getOption('ylabel')) { + // It would make sense to shift the chart here to make room for the y-axis + // label, but the default yAxisLabelWidth is large enough that this results + // in overly-padded charts. The y-axis label should fit fine. If it + // doesn't, the yAxisLabelWidth option can be increased. + var y_rect = e.reserveSpaceLeft(0); + + this.ylabel_div_ = createRotatedDiv(g, y_rect, 1, // primary (left) y-axis + 'dygraph-label dygraph-ylabel', g.getOption('ylabel')); + div.appendChild(this.ylabel_div_); + } + + if (g.getOption('y2label') && g.numAxes() == 2) { + // same logic applies here as for ylabel. + var y2_rect = e.reserveSpaceRight(0); + this.y2label_div_ = createRotatedDiv(g, y2_rect, 2, // secondary (right) y-axis + 'dygraph-label dygraph-y2label', g.getOption('y2label')); + div.appendChild(this.y2label_div_); + } +}; + +chart_labels.prototype.didDrawChart = function (e) { + var g = e.dygraph; + if (this.title_div_) { + this.title_div_.children[0].innerHTML = g.getOption('title'); + } + if (this.xlabel_div_) { + this.xlabel_div_.children[0].innerHTML = g.getOption('xlabel'); + } + if (this.ylabel_div_) { + this.ylabel_div_.children[0].children[0].innerHTML = g.getOption('ylabel'); + } + if (this.y2label_div_) { + this.y2label_div_.children[0].children[0].innerHTML = g.getOption('y2label'); + } +}; + +chart_labels.prototype.clearChart = function () {}; + +chart_labels.prototype.destroy = function () { + this.detachLabels_(); +}; + +exports["default"] = chart_labels; +module.exports = exports["default"]; + +},{}],23:[function(require,module,exports){ +/** + * @license + * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ +/*global Dygraph:false */ + +/* + +Current bits of jankiness: +- Direct layout access +- Direct area access + +*/ + +"use strict"; + +/** + * Draws the gridlines, i.e. the gray horizontal & vertical lines running the + * length of the chart. + * + * @constructor + */ +Object.defineProperty(exports, "__esModule", { + value: true +}); +var grid = function grid() {}; + +grid.prototype.toString = function () { + return "Gridline Plugin"; +}; + +grid.prototype.activate = function (g) { + return { + willDrawChart: this.willDrawChart + }; +}; + +grid.prototype.willDrawChart = function (e) { + // Draw the new X/Y grid. Lines appear crisper when pixels are rounded to + // half-integers. This prevents them from drawing in two rows/cols. + var g = e.dygraph; + var ctx = e.drawingContext; + var layout = g.layout_; + var area = e.dygraph.plotter_.area; + + function halfUp(x) { + return Math.round(x) + 0.5; + } + function halfDown(y) { + return Math.round(y) - 0.5; + } + + var x, y, i, ticks; + if (g.getOptionForAxis('drawGrid', 'y')) { + var axes = ["y", "y2"]; + var strokeStyles = [], + lineWidths = [], + drawGrid = [], + stroking = [], + strokePattern = []; + for (var i = 0; i < axes.length; i++) { + drawGrid[i] = g.getOptionForAxis('drawGrid', axes[i]); + if (drawGrid[i]) { + strokeStyles[i] = g.getOptionForAxis('gridLineColor', axes[i]); + lineWidths[i] = g.getOptionForAxis('gridLineWidth', axes[i]); + strokePattern[i] = g.getOptionForAxis('gridLinePattern', axes[i]); + stroking[i] = strokePattern[i] && strokePattern[i].length >= 2; + } + } + ticks = layout.yticks; + ctx.save(); + // draw grids for the different y axes + ticks.forEach(function (tick) { + if (!tick.has_tick) return; + var axis = tick.axis; + if (drawGrid[axis]) { + ctx.save(); + if (stroking[axis]) { + if (ctx.setLineDash) ctx.setLineDash(strokePattern[axis]); + } + ctx.strokeStyle = strokeStyles[axis]; + ctx.lineWidth = lineWidths[axis]; + + x = halfUp(area.x); + y = halfDown(area.y + tick.pos * area.h); + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x + area.w, y); + ctx.stroke(); + + ctx.restore(); + } + }); + ctx.restore(); + } + + // draw grid for x axis + if (g.getOptionForAxis('drawGrid', 'x')) { + ticks = layout.xticks; + ctx.save(); + var strokePattern = g.getOptionForAxis('gridLinePattern', 'x'); + var stroking = strokePattern && strokePattern.length >= 2; + if (stroking) { + if (ctx.setLineDash) ctx.setLineDash(strokePattern); + } + ctx.strokeStyle = g.getOptionForAxis('gridLineColor', 'x'); + ctx.lineWidth = g.getOptionForAxis('gridLineWidth', 'x'); + ticks.forEach(function (tick) { + if (!tick.has_tick) return; + x = halfUp(area.x + tick.pos * area.w); + y = halfDown(area.y + area.h); + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x, area.y); + ctx.closePath(); + ctx.stroke(); + }); + if (stroking) { + if (ctx.setLineDash) ctx.setLineDash([]); + } + ctx.restore(); + } +}; + +grid.prototype.destroy = function () {}; + +exports["default"] = grid; +module.exports = exports["default"]; + +},{}],24:[function(require,module,exports){ +/** + * @license + * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ +/*global Dygraph:false */ + +/* +Current bits of jankiness: +- Uses two private APIs: + 1. Dygraph.optionsViewForAxis_ + 2. dygraph.plotter_.area +- Registers for a "predraw" event, which should be renamed. +- I call calculateEmWidthInDiv more often than needed. +*/ + +/*global Dygraph:false */ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj["default"] = obj; return newObj; } } + +var _dygraphUtils = require('../dygraph-utils'); + +var utils = _interopRequireWildcard(_dygraphUtils); + +/** + * Creates the legend, which appears when the user hovers over the chart. + * The legend can be either a user-specified or generated div. + * + * @constructor + */ +var Legend = function Legend() { + this.legend_div_ = null; + this.is_generated_div_ = false; // do we own this div, or was it user-specified? +}; + +Legend.prototype.toString = function () { + return "Legend Plugin"; +}; + +/** + * This is called during the dygraph constructor, after options have been set + * but before the data is available. + * + * Proper tasks to do here include: + * - Reading your own options + * - DOM manipulation + * - Registering event listeners + * + * @param {Dygraph} g Graph instance. + * @return {object.} Mapping of event names to callbacks. + */ +Legend.prototype.activate = function (g) { + var div; + + var userLabelsDiv = g.getOption('labelsDiv'); + if (userLabelsDiv && null !== userLabelsDiv) { + if (typeof userLabelsDiv == "string" || userLabelsDiv instanceof String) { + div = document.getElementById(userLabelsDiv); + } else { + div = userLabelsDiv; + } + } else { + div = document.createElement("div"); + div.className = "dygraph-legend"; + // TODO(danvk): come up with a cleaner way to expose this. + g.graphDiv.appendChild(div); + this.is_generated_div_ = true; + } + + this.legend_div_ = div; + this.one_em_width_ = 10; // just a guess, will be updated. + + return { + select: this.select, + deselect: this.deselect, + // TODO(danvk): rethink the name "predraw" before we commit to it in any API. + predraw: this.predraw, + didDrawChart: this.didDrawChart + }; +}; + +// Needed for dashed lines. +var calculateEmWidthInDiv = function calculateEmWidthInDiv(div) { + var sizeSpan = document.createElement('span'); + sizeSpan.setAttribute('style', 'margin: 0; padding: 0 0 0 1em; border: 0;'); + div.appendChild(sizeSpan); + var oneEmWidth = sizeSpan.offsetWidth; + div.removeChild(sizeSpan); + return oneEmWidth; +}; + +var escapeHTML = function escapeHTML(str) { + return str.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); +}; + +Legend.prototype.select = function (e) { + var xValue = e.selectedX; + var points = e.selectedPoints; + var row = e.selectedRow; + + var legendMode = e.dygraph.getOption('legend'); + if (legendMode === 'never') { + this.legend_div_.style.display = 'none'; + return; + } + + if (legendMode === 'follow') { + // create floating legend div + var area = e.dygraph.plotter_.area; + var labelsDivWidth = this.legend_div_.offsetWidth; + var yAxisLabelWidth = e.dygraph.getOptionForAxis('axisLabelWidth', 'y'); + // determine floating [left, top] coordinates of the legend div + // within the plotter_ area + // offset 50 px to the right and down from the first selection point + // 50 px is guess based on mouse cursor size + var leftLegend = points[0].x * area.w + 50; + var topLegend = points[0].y * area.h - 50; + + // if legend floats to end of the chart area, it flips to the other + // side of the selection point + if (leftLegend + labelsDivWidth + 1 > area.w) { + leftLegend = leftLegend - 2 * 50 - labelsDivWidth - (yAxisLabelWidth - area.x); + } + + e.dygraph.graphDiv.appendChild(this.legend_div_); + this.legend_div_.style.left = yAxisLabelWidth + leftLegend + "px"; + this.legend_div_.style.top = topLegend + "px"; + } + + var html = Legend.generateLegendHTML(e.dygraph, xValue, points, this.one_em_width_, row); + this.legend_div_.innerHTML = html; + this.legend_div_.style.display = ''; +}; + +Legend.prototype.deselect = function (e) { + var legendMode = e.dygraph.getOption('legend'); + if (legendMode !== 'always') { + this.legend_div_.style.display = "none"; + } + + // Have to do this every time, since styles might have changed. + var oneEmWidth = calculateEmWidthInDiv(this.legend_div_); + this.one_em_width_ = oneEmWidth; + + var html = Legend.generateLegendHTML(e.dygraph, undefined, undefined, oneEmWidth, null); + this.legend_div_.innerHTML = html; +}; + +Legend.prototype.didDrawChart = function (e) { + this.deselect(e); +}; + +// Right edge should be flush with the right edge of the charting area (which +// may not be the same as the right edge of the div, if we have two y-axes. +// TODO(danvk): is any of this really necessary? Could just set "right" in "activate". +/** + * Position the labels div so that: + * - its right edge is flush with the right edge of the charting area + * - its top edge is flush with the top edge of the charting area + * @private + */ +Legend.prototype.predraw = function (e) { + // Don't touch a user-specified labelsDiv. + if (!this.is_generated_div_) return; + + // TODO(danvk): only use real APIs for this. + e.dygraph.graphDiv.appendChild(this.legend_div_); + var area = e.dygraph.getArea(); + var labelsDivWidth = this.legend_div_.offsetWidth; + this.legend_div_.style.left = area.x + area.w - labelsDivWidth - 1 + "px"; + this.legend_div_.style.top = area.y + "px"; +}; + +/** + * Called when dygraph.destroy() is called. + * You should null out any references and detach any DOM elements. + */ +Legend.prototype.destroy = function () { + this.legend_div_ = null; +}; + +/** + * Generates HTML for the legend which is displayed when hovering over the + * chart. If no selected points are specified, a default legend is returned + * (this may just be the empty string). + * @param {number} x The x-value of the selected points. + * @param {Object} sel_points List of selected points for the given + * x-value. Should have properties like 'name', 'yval' and 'canvasy'. + * @param {number} oneEmWidth The pixel width for 1em in the legend. Only + * relevant when displaying a legend with no selection (i.e. {legend: + * 'always'}) and with dashed lines. + * @param {number} row The selected row index. + * @private + */ +Legend.generateLegendHTML = function (g, x, sel_points, oneEmWidth, row) { + // Data about the selection to pass to legendFormatter + var data = { + dygraph: g, + x: x, + series: [] + }; + + var labelToSeries = {}; + var labels = g.getLabels(); + if (labels) { + for (var i = 1; i < labels.length; i++) { + var series = g.getPropertiesForSeries(labels[i]); + var strokePattern = g.getOption('strokePattern', labels[i]); + var seriesData = { + dashHTML: generateLegendDashHTML(strokePattern, series.color, oneEmWidth), + label: labels[i], + labelHTML: escapeHTML(labels[i]), + isVisible: series.visible, + color: series.color + }; + + data.series.push(seriesData); + labelToSeries[labels[i]] = seriesData; + } + } + + if (typeof x !== 'undefined') { + var xOptView = g.optionsViewForAxis_('x'); + var xvf = xOptView('valueFormatter'); + data.xHTML = xvf.call(g, x, xOptView, labels[0], g, row, 0); + + var yOptViews = []; + var num_axes = g.numAxes(); + for (var i = 0; i < num_axes; i++) { + // TODO(danvk): remove this use of a private API + yOptViews[i] = g.optionsViewForAxis_('y' + (i ? 1 + i : '')); + } + + var showZeros = g.getOption('labelsShowZeroValues'); + var highlightSeries = g.getHighlightSeries(); + for (i = 0; i < sel_points.length; i++) { + var pt = sel_points[i]; + var seriesData = labelToSeries[pt.name]; + seriesData.y = pt.yval; + + if (pt.yval === 0 && !showZeros || isNaN(pt.canvasy)) { + seriesData.isVisible = false; + continue; + } + + var series = g.getPropertiesForSeries(pt.name); + var yOptView = yOptViews[series.axis - 1]; + var fmtFunc = yOptView('valueFormatter'); + var yHTML = fmtFunc.call(g, pt.yval, yOptView, pt.name, g, row, labels.indexOf(pt.name)); + + utils.update(seriesData, { yHTML: yHTML }); + + if (pt.name == highlightSeries) { + seriesData.isHighlighted = true; + } + } + } + + var formatter = g.getOption('legendFormatter') || Legend.defaultFormatter; + return formatter.call(g, data); +}; + +Legend.defaultFormatter = function (data) { + var g = data.dygraph; + + // TODO(danvk): deprecate this option in place of {legend: 'never'} + // XXX should this logic be in the formatter? + if (g.getOption('showLabelsOnHighlight') !== true) return ''; + + var sepLines = g.getOption('labelsSeparateLines'); + var html; + + if (typeof data.x === 'undefined') { + // TODO: this check is duplicated in generateLegendHTML. Put it in one place. + if (g.getOption('legend') != 'always') { + return ''; + } + + html = ''; + for (var i = 0; i < data.series.length; i++) { + var series = data.series[i]; + if (!series.isVisible) continue; + + if (html !== '') html += sepLines ? '
' : ' '; + html += "" + series.dashHTML + " " + series.labelHTML + ""; + } + return html; + } + + html = data.xHTML + ':'; + for (var i = 0; i < data.series.length; i++) { + var series = data.series[i]; + if (!series.isVisible) continue; + if (sepLines) html += '
'; + var cls = series.isHighlighted ? ' class="highlight"' : ''; + html += " " + series.labelHTML + ": " + series.yHTML + ""; + } + return html; +}; + +/** + * Generates html for the "dash" displayed on the legend when using "legend: always". + * In particular, this works for dashed lines with any stroke pattern. It will + * try to scale the pattern to fit in 1em width. Or if small enough repeat the + * pattern for 1em width. + * + * @param strokePattern The pattern + * @param color The color of the series. + * @param oneEmWidth The width in pixels of 1em in the legend. + * @private + */ +// TODO(danvk): cache the results of this +function generateLegendDashHTML(strokePattern, color, oneEmWidth) { + // Easy, common case: a solid line + if (!strokePattern || strokePattern.length <= 1) { + return "
"; + } + + var i, j, paddingLeft, marginRight; + var strokePixelLength = 0, + segmentLoop = 0; + var normalizedPattern = []; + var loop; + + // Compute the length of the pixels including the first segment twice, + // since we repeat it. + for (i = 0; i <= strokePattern.length; i++) { + strokePixelLength += strokePattern[i % strokePattern.length]; + } + + // See if we can loop the pattern by itself at least twice. + loop = Math.floor(oneEmWidth / (strokePixelLength - strokePattern[0])); + if (loop > 1) { + // This pattern fits at least two times, no scaling just convert to em; + for (i = 0; i < strokePattern.length; i++) { + normalizedPattern[i] = strokePattern[i] / oneEmWidth; + } + // Since we are repeating the pattern, we don't worry about repeating the + // first segment in one draw. + segmentLoop = normalizedPattern.length; + } else { + // If the pattern doesn't fit in the legend we scale it to fit. + loop = 1; + for (i = 0; i < strokePattern.length; i++) { + normalizedPattern[i] = strokePattern[i] / strokePixelLength; + } + // For the scaled patterns we do redraw the first segment. + segmentLoop = normalizedPattern.length + 1; + } + + // Now make the pattern. + var dash = ""; + for (j = 0; j < loop; j++) { + for (i = 0; i < segmentLoop; i += 2) { + // The padding is the drawn segment. + paddingLeft = normalizedPattern[i % normalizedPattern.length]; + if (i < strokePattern.length) { + // The margin is the space segment. + marginRight = normalizedPattern[(i + 1) % normalizedPattern.length]; + } else { + // The repeated first segment has no right margin. + marginRight = 0; + } + dash += "
"; + } + } + return dash; +}; + +exports["default"] = Legend; +module.exports = exports["default"]; + +},{"../dygraph-utils":17}],25:[function(require,module,exports){ +/** + * @license + * Copyright 2011 Paul Felix (paul.eric.felix@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ +/*global Dygraph:false,TouchEvent:false */ + +/** + * @fileoverview This file contains the RangeSelector plugin used to provide + * a timeline range selector widget for dygraphs. + */ + +/*global Dygraph:false */ +"use strict"; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +var _dygraphUtils = require('../dygraph-utils'); + +var utils = _interopRequireWildcard(_dygraphUtils); + +var _dygraphInteractionModel = require('../dygraph-interaction-model'); + +var _dygraphInteractionModel2 = _interopRequireDefault(_dygraphInteractionModel); + +var _iframeTarp = require('../iframe-tarp'); + +var _iframeTarp2 = _interopRequireDefault(_iframeTarp); + +var rangeSelector = function rangeSelector() { + this.hasTouchInterface_ = typeof TouchEvent != 'undefined'; + this.isMobileDevice_ = /mobile|android/gi.test(navigator.appVersion); + this.interfaceCreated_ = false; +}; + +rangeSelector.prototype.toString = function () { + return "RangeSelector Plugin"; +}; + +rangeSelector.prototype.activate = function (dygraph) { + this.dygraph_ = dygraph; + if (this.getOption_('showRangeSelector')) { + this.createInterface_(); + } + return { + layout: this.reserveSpace_, + predraw: this.renderStaticLayer_, + didDrawChart: this.renderInteractiveLayer_ + }; +}; + +rangeSelector.prototype.destroy = function () { + this.bgcanvas_ = null; + this.fgcanvas_ = null; + this.leftZoomHandle_ = null; + this.rightZoomHandle_ = null; +}; + +//------------------------------------------------------------------ +// Private methods +//------------------------------------------------------------------ + +rangeSelector.prototype.getOption_ = function (name, opt_series) { + return this.dygraph_.getOption(name, opt_series); +}; + +rangeSelector.prototype.setDefaultOption_ = function (name, value) { + this.dygraph_.attrs_[name] = value; +}; + +/** + * @private + * Creates the range selector elements and adds them to the graph. + */ +rangeSelector.prototype.createInterface_ = function () { + this.createCanvases_(); + this.createZoomHandles_(); + this.initInteraction_(); + + // Range selector and animatedZooms have a bad interaction. See issue 359. + if (this.getOption_('animatedZooms')) { + console.warn('Animated zooms and range selector are not compatible; disabling animatedZooms.'); + this.dygraph_.updateOptions({ animatedZooms: false }, true); + } + + this.interfaceCreated_ = true; + this.addToGraph_(); +}; + +/** + * @private + * Adds the range selector to the graph. + */ +rangeSelector.prototype.addToGraph_ = function () { + var graphDiv = this.graphDiv_ = this.dygraph_.graphDiv; + graphDiv.appendChild(this.bgcanvas_); + graphDiv.appendChild(this.fgcanvas_); + graphDiv.appendChild(this.leftZoomHandle_); + graphDiv.appendChild(this.rightZoomHandle_); +}; + +/** + * @private + * Removes the range selector from the graph. + */ +rangeSelector.prototype.removeFromGraph_ = function () { + var graphDiv = this.graphDiv_; + graphDiv.removeChild(this.bgcanvas_); + graphDiv.removeChild(this.fgcanvas_); + graphDiv.removeChild(this.leftZoomHandle_); + graphDiv.removeChild(this.rightZoomHandle_); + this.graphDiv_ = null; +}; + +/** + * @private + * Called by Layout to allow range selector to reserve its space. + */ +rangeSelector.prototype.reserveSpace_ = function (e) { + if (this.getOption_('showRangeSelector')) { + e.reserveSpaceBottom(this.getOption_('rangeSelectorHeight') + 4); + } +}; + +/** + * @private + * Renders the static portion of the range selector at the predraw stage. + */ +rangeSelector.prototype.renderStaticLayer_ = function () { + if (!this.updateVisibility_()) { + return; + } + this.resize_(); + this.drawStaticLayer_(); +}; + +/** + * @private + * Renders the interactive portion of the range selector after the chart has been drawn. + */ +rangeSelector.prototype.renderInteractiveLayer_ = function () { + if (!this.updateVisibility_() || this.isChangingRange_) { + return; + } + this.placeZoomHandles_(); + this.drawInteractiveLayer_(); +}; + +/** + * @private + * Check to see if the range selector is enabled/disabled and update visibility accordingly. + */ +rangeSelector.prototype.updateVisibility_ = function () { + var enabled = this.getOption_('showRangeSelector'); + if (enabled) { + if (!this.interfaceCreated_) { + this.createInterface_(); + } else if (!this.graphDiv_ || !this.graphDiv_.parentNode) { + this.addToGraph_(); + } + } else if (this.graphDiv_) { + this.removeFromGraph_(); + var dygraph = this.dygraph_; + setTimeout(function () { + dygraph.width_ = 0;dygraph.resize(); + }, 1); + } + return enabled; +}; + +/** + * @private + * Resizes the range selector. + */ +rangeSelector.prototype.resize_ = function () { + function setElementRect(canvas, context, rect, pixelRatioOption) { + var canvasScale = pixelRatioOption || utils.getContextPixelRatio(context); + + canvas.style.top = rect.y + 'px'; + canvas.style.left = rect.x + 'px'; + canvas.width = rect.w * canvasScale; + canvas.height = rect.h * canvasScale; + canvas.style.width = rect.w + 'px'; + canvas.style.height = rect.h + 'px'; + + if (canvasScale != 1) { + context.scale(canvasScale, canvasScale); + } + } + + var plotArea = this.dygraph_.layout_.getPlotArea(); + + var xAxisLabelHeight = 0; + if (this.dygraph_.getOptionForAxis('drawAxis', 'x')) { + xAxisLabelHeight = this.getOption_('xAxisHeight') || this.getOption_('axisLabelFontSize') + 2 * this.getOption_('axisTickSize'); + } + this.canvasRect_ = { + x: plotArea.x, + y: plotArea.y + plotArea.h + xAxisLabelHeight + 4, + w: plotArea.w, + h: this.getOption_('rangeSelectorHeight') + }; + + var pixelRatioOption = this.dygraph_.getNumericOption('pixelRatio'); + setElementRect(this.bgcanvas_, this.bgcanvas_ctx_, this.canvasRect_, pixelRatioOption); + setElementRect(this.fgcanvas_, this.fgcanvas_ctx_, this.canvasRect_, pixelRatioOption); +}; + +/** + * @private + * Creates the background and foreground canvases. + */ +rangeSelector.prototype.createCanvases_ = function () { + this.bgcanvas_ = utils.createCanvas(); + this.bgcanvas_.className = 'dygraph-rangesel-bgcanvas'; + this.bgcanvas_.style.position = 'absolute'; + this.bgcanvas_.style.zIndex = 9; + this.bgcanvas_ctx_ = utils.getContext(this.bgcanvas_); + + this.fgcanvas_ = utils.createCanvas(); + this.fgcanvas_.className = 'dygraph-rangesel-fgcanvas'; + this.fgcanvas_.style.position = 'absolute'; + this.fgcanvas_.style.zIndex = 9; + this.fgcanvas_.style.cursor = 'default'; + this.fgcanvas_ctx_ = utils.getContext(this.fgcanvas_); +}; + +/** + * @private + * Creates the zoom handle elements. + */ +rangeSelector.prototype.createZoomHandles_ = function () { + var img = new Image(); + img.className = 'dygraph-rangesel-zoomhandle'; + img.style.position = 'absolute'; + img.style.zIndex = 10; + img.style.visibility = 'hidden'; // Initially hidden so they don't show up in the wrong place. + img.style.cursor = 'col-resize'; + // TODO: change image to more options + img.width = 9; + img.height = 16; + img.src = 'data:image/png;base64,' + 'iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAA' + 'zwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENv' + 'bW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl' + '6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7s' + 'qSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII='; + + if (this.isMobileDevice_) { + img.width *= 2; + img.height *= 2; + } + + this.leftZoomHandle_ = img; + this.rightZoomHandle_ = img.cloneNode(false); +}; + +/** + * @private + * Sets up the interaction for the range selector. + */ +rangeSelector.prototype.initInteraction_ = function () { + var self = this; + var topElem = document; + var clientXLast = 0; + var handle = null; + var isZooming = false; + var isPanning = false; + var dynamic = !this.isMobileDevice_; + + // We cover iframes during mouse interactions. See comments in + // dygraph-utils.js for more info on why this is a good idea. + var tarp = new _iframeTarp2['default'](); + + // functions, defined below. Defining them this way (rather than with + // "function foo() {...}" makes JSHint happy. + var toXDataWindow, onZoomStart, onZoom, onZoomEnd, doZoom, isMouseInPanZone, onPanStart, onPan, onPanEnd, doPan, onCanvasHover; + + // Touch event functions + var onZoomHandleTouchEvent, onCanvasTouchEvent, addTouchEvents; + + toXDataWindow = function (zoomHandleStatus) { + var xDataLimits = self.dygraph_.xAxisExtremes(); + var fact = (xDataLimits[1] - xDataLimits[0]) / self.canvasRect_.w; + var xDataMin = xDataLimits[0] + (zoomHandleStatus.leftHandlePos - self.canvasRect_.x) * fact; + var xDataMax = xDataLimits[0] + (zoomHandleStatus.rightHandlePos - self.canvasRect_.x) * fact; + return [xDataMin, xDataMax]; + }; + + onZoomStart = function (e) { + utils.cancelEvent(e); + isZooming = true; + clientXLast = e.clientX; + handle = e.target ? e.target : e.srcElement; + if (e.type === 'mousedown' || e.type === 'dragstart') { + // These events are removed manually. + utils.addEvent(topElem, 'mousemove', onZoom); + utils.addEvent(topElem, 'mouseup', onZoomEnd); + } + self.fgcanvas_.style.cursor = 'col-resize'; + tarp.cover(); + return true; + }; + + onZoom = function (e) { + if (!isZooming) { + return false; + } + utils.cancelEvent(e); + + var delX = e.clientX - clientXLast; + if (Math.abs(delX) < 4) { + return true; + } + clientXLast = e.clientX; + + // Move handle. + var zoomHandleStatus = self.getZoomHandleStatus_(); + var newPos; + if (handle == self.leftZoomHandle_) { + newPos = zoomHandleStatus.leftHandlePos + delX; + newPos = Math.min(newPos, zoomHandleStatus.rightHandlePos - handle.width - 3); + newPos = Math.max(newPos, self.canvasRect_.x); + } else { + newPos = zoomHandleStatus.rightHandlePos + delX; + newPos = Math.min(newPos, self.canvasRect_.x + self.canvasRect_.w); + newPos = Math.max(newPos, zoomHandleStatus.leftHandlePos + handle.width + 3); + } + var halfHandleWidth = handle.width / 2; + handle.style.left = newPos - halfHandleWidth + 'px'; + self.drawInteractiveLayer_(); + + // Zoom on the fly. + if (dynamic) { + doZoom(); + } + return true; + }; + + onZoomEnd = function (e) { + if (!isZooming) { + return false; + } + isZooming = false; + tarp.uncover(); + utils.removeEvent(topElem, 'mousemove', onZoom); + utils.removeEvent(topElem, 'mouseup', onZoomEnd); + self.fgcanvas_.style.cursor = 'default'; + + // If on a slower device, zoom now. + if (!dynamic) { + doZoom(); + } + return true; + }; + + doZoom = function () { + try { + var zoomHandleStatus = self.getZoomHandleStatus_(); + self.isChangingRange_ = true; + if (!zoomHandleStatus.isZoomed) { + self.dygraph_.resetZoom(); + } else { + var xDataWindow = toXDataWindow(zoomHandleStatus); + self.dygraph_.doZoomXDates_(xDataWindow[0], xDataWindow[1]); + } + } finally { + self.isChangingRange_ = false; + } + }; + + isMouseInPanZone = function (e) { + var rect = self.leftZoomHandle_.getBoundingClientRect(); + var leftHandleClientX = rect.left + rect.width / 2; + rect = self.rightZoomHandle_.getBoundingClientRect(); + var rightHandleClientX = rect.left + rect.width / 2; + return e.clientX > leftHandleClientX && e.clientX < rightHandleClientX; + }; + + onPanStart = function (e) { + if (!isPanning && isMouseInPanZone(e) && self.getZoomHandleStatus_().isZoomed) { + utils.cancelEvent(e); + isPanning = true; + clientXLast = e.clientX; + if (e.type === 'mousedown') { + // These events are removed manually. + utils.addEvent(topElem, 'mousemove', onPan); + utils.addEvent(topElem, 'mouseup', onPanEnd); + } + return true; + } + return false; + }; + + onPan = function (e) { + if (!isPanning) { + return false; + } + utils.cancelEvent(e); + + var delX = e.clientX - clientXLast; + if (Math.abs(delX) < 4) { + return true; + } + clientXLast = e.clientX; + + // Move range view + var zoomHandleStatus = self.getZoomHandleStatus_(); + var leftHandlePos = zoomHandleStatus.leftHandlePos; + var rightHandlePos = zoomHandleStatus.rightHandlePos; + var rangeSize = rightHandlePos - leftHandlePos; + if (leftHandlePos + delX <= self.canvasRect_.x) { + leftHandlePos = self.canvasRect_.x; + rightHandlePos = leftHandlePos + rangeSize; + } else if (rightHandlePos + delX >= self.canvasRect_.x + self.canvasRect_.w) { + rightHandlePos = self.canvasRect_.x + self.canvasRect_.w; + leftHandlePos = rightHandlePos - rangeSize; + } else { + leftHandlePos += delX; + rightHandlePos += delX; + } + var halfHandleWidth = self.leftZoomHandle_.width / 2; + self.leftZoomHandle_.style.left = leftHandlePos - halfHandleWidth + 'px'; + self.rightZoomHandle_.style.left = rightHandlePos - halfHandleWidth + 'px'; + self.drawInteractiveLayer_(); + + // Do pan on the fly. + if (dynamic) { + doPan(); + } + return true; + }; + + onPanEnd = function (e) { + if (!isPanning) { + return false; + } + isPanning = false; + utils.removeEvent(topElem, 'mousemove', onPan); + utils.removeEvent(topElem, 'mouseup', onPanEnd); + // If on a slower device, do pan now. + if (!dynamic) { + doPan(); + } + return true; + }; + + doPan = function () { + try { + self.isChangingRange_ = true; + self.dygraph_.dateWindow_ = toXDataWindow(self.getZoomHandleStatus_()); + self.dygraph_.drawGraph_(false); + } finally { + self.isChangingRange_ = false; + } + }; + + onCanvasHover = function (e) { + if (isZooming || isPanning) { + return; + } + var cursor = isMouseInPanZone(e) ? 'move' : 'default'; + if (cursor != self.fgcanvas_.style.cursor) { + self.fgcanvas_.style.cursor = cursor; + } + }; + + onZoomHandleTouchEvent = function (e) { + if (e.type == 'touchstart' && e.targetTouches.length == 1) { + if (onZoomStart(e.targetTouches[0])) { + utils.cancelEvent(e); + } + } else if (e.type == 'touchmove' && e.targetTouches.length == 1) { + if (onZoom(e.targetTouches[0])) { + utils.cancelEvent(e); + } + } else { + onZoomEnd(e); + } + }; + + onCanvasTouchEvent = function (e) { + if (e.type == 'touchstart' && e.targetTouches.length == 1) { + if (onPanStart(e.targetTouches[0])) { + utils.cancelEvent(e); + } + } else if (e.type == 'touchmove' && e.targetTouches.length == 1) { + if (onPan(e.targetTouches[0])) { + utils.cancelEvent(e); + } + } else { + onPanEnd(e); + } + }; + + addTouchEvents = function (elem, fn) { + var types = ['touchstart', 'touchend', 'touchmove', 'touchcancel']; + for (var i = 0; i < types.length; i++) { + self.dygraph_.addAndTrackEvent(elem, types[i], fn); + } + }; + + this.setDefaultOption_('interactionModel', _dygraphInteractionModel2['default'].dragIsPanInteractionModel); + this.setDefaultOption_('panEdgeFraction', 0.0001); + + var dragStartEvent = window.opera ? 'mousedown' : 'dragstart'; + this.dygraph_.addAndTrackEvent(this.leftZoomHandle_, dragStartEvent, onZoomStart); + this.dygraph_.addAndTrackEvent(this.rightZoomHandle_, dragStartEvent, onZoomStart); + + this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousedown', onPanStart); + this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousemove', onCanvasHover); + + // Touch events + if (this.hasTouchInterface_) { + addTouchEvents(this.leftZoomHandle_, onZoomHandleTouchEvent); + addTouchEvents(this.rightZoomHandle_, onZoomHandleTouchEvent); + addTouchEvents(this.fgcanvas_, onCanvasTouchEvent); + } +}; + +/** + * @private + * Draws the static layer in the background canvas. + */ +rangeSelector.prototype.drawStaticLayer_ = function () { + var ctx = this.bgcanvas_ctx_; + ctx.clearRect(0, 0, this.canvasRect_.w, this.canvasRect_.h); + try { + this.drawMiniPlot_(); + } catch (ex) { + console.warn(ex); + } + + var margin = 0.5; + this.bgcanvas_ctx_.lineWidth = this.getOption_('rangeSelectorBackgroundLineWidth'); + ctx.strokeStyle = this.getOption_('rangeSelectorBackgroundStrokeColor'); + ctx.beginPath(); + ctx.moveTo(margin, margin); + ctx.lineTo(margin, this.canvasRect_.h - margin); + ctx.lineTo(this.canvasRect_.w - margin, this.canvasRect_.h - margin); + ctx.lineTo(this.canvasRect_.w - margin, margin); + ctx.stroke(); +}; + +/** + * @private + * Draws the mini plot in the background canvas. + */ +rangeSelector.prototype.drawMiniPlot_ = function () { + var fillStyle = this.getOption_('rangeSelectorPlotFillColor'); + var fillGradientStyle = this.getOption_('rangeSelectorPlotFillGradientColor'); + var strokeStyle = this.getOption_('rangeSelectorPlotStrokeColor'); + if (!fillStyle && !strokeStyle) { + return; + } + + var stepPlot = this.getOption_('stepPlot'); + + var combinedSeriesData = this.computeCombinedSeriesAndLimits_(); + var yRange = combinedSeriesData.yMax - combinedSeriesData.yMin; + + // Draw the mini plot. + var ctx = this.bgcanvas_ctx_; + var margin = 0.5; + + var xExtremes = this.dygraph_.xAxisExtremes(); + var xRange = Math.max(xExtremes[1] - xExtremes[0], 1.e-30); + var xFact = (this.canvasRect_.w - margin) / xRange; + var yFact = (this.canvasRect_.h - margin) / yRange; + var canvasWidth = this.canvasRect_.w - margin; + var canvasHeight = this.canvasRect_.h - margin; + + var prevX = null, + prevY = null; + + ctx.beginPath(); + ctx.moveTo(margin, canvasHeight); + for (var i = 0; i < combinedSeriesData.data.length; i++) { + var dataPoint = combinedSeriesData.data[i]; + var x = dataPoint[0] !== null ? (dataPoint[0] - xExtremes[0]) * xFact : NaN; + var y = dataPoint[1] !== null ? canvasHeight - (dataPoint[1] - combinedSeriesData.yMin) * yFact : NaN; + + // Skip points that don't change the x-value. Overly fine-grained points + // can cause major slowdowns with the ctx.fill() call below. + if (!stepPlot && prevX !== null && Math.round(x) == Math.round(prevX)) { + continue; + } + + if (isFinite(x) && isFinite(y)) { + if (prevX === null) { + ctx.lineTo(x, canvasHeight); + } else if (stepPlot) { + ctx.lineTo(x, prevY); + } + ctx.lineTo(x, y); + prevX = x; + prevY = y; + } else { + if (prevX !== null) { + if (stepPlot) { + ctx.lineTo(x, prevY); + ctx.lineTo(x, canvasHeight); + } else { + ctx.lineTo(prevX, canvasHeight); + } + } + prevX = prevY = null; + } + } + ctx.lineTo(canvasWidth, canvasHeight); + ctx.closePath(); + + if (fillStyle) { + var lingrad = this.bgcanvas_ctx_.createLinearGradient(0, 0, 0, canvasHeight); + if (fillGradientStyle) { + lingrad.addColorStop(0, fillGradientStyle); + } + lingrad.addColorStop(1, fillStyle); + this.bgcanvas_ctx_.fillStyle = lingrad; + ctx.fill(); + } + + if (strokeStyle) { + this.bgcanvas_ctx_.strokeStyle = strokeStyle; + this.bgcanvas_ctx_.lineWidth = this.getOption_('rangeSelectorPlotLineWidth'); + ctx.stroke(); + } +}; + +/** + * @private + * Computes and returns the combined series data along with min/max for the mini plot. + * The combined series consists of averaged values for all series. + * When series have error bars, the error bars are ignored. + * @return {Object} An object containing combined series array, ymin, ymax. + */ +rangeSelector.prototype.computeCombinedSeriesAndLimits_ = function () { + var g = this.dygraph_; + var logscale = this.getOption_('logscale'); + var i; + + // Select series to combine. By default, all series are combined. + var numColumns = g.numColumns(); + var labels = g.getLabels(); + var includeSeries = new Array(numColumns); + var anySet = false; + var visibility = g.visibility(); + var inclusion = []; + + for (i = 1; i < numColumns; i++) { + var include = this.getOption_('showInRangeSelector', labels[i]); + inclusion.push(include); + if (include !== null) anySet = true; // it's set explicitly for this series + } + + if (anySet) { + for (i = 1; i < numColumns; i++) { + includeSeries[i] = inclusion[i - 1]; + } + } else { + for (i = 1; i < numColumns; i++) { + includeSeries[i] = visibility[i - 1]; + } + } + + // Create a combined series (average of selected series values). + // TODO(danvk): short-circuit if there's only one series. + var rolledSeries = []; + var dataHandler = g.dataHandler_; + var options = g.attributes_; + for (i = 1; i < g.numColumns(); i++) { + if (!includeSeries[i]) continue; + var series = dataHandler.extractSeries(g.rawData_, i, options); + if (g.rollPeriod() > 1) { + series = dataHandler.rollingAverage(series, g.rollPeriod(), options); + } + + rolledSeries.push(series); + } + + var combinedSeries = []; + for (i = 0; i < rolledSeries[0].length; i++) { + var sum = 0; + var count = 0; + for (var j = 0; j < rolledSeries.length; j++) { + var y = rolledSeries[j][i][1]; + if (y === null || isNaN(y)) continue; + count++; + sum += y; + } + combinedSeries.push([rolledSeries[0][i][0], sum / count]); + } + + // Compute the y range. + var yMin = Number.MAX_VALUE; + var yMax = -Number.MAX_VALUE; + for (i = 0; i < combinedSeries.length; i++) { + var yVal = combinedSeries[i][1]; + if (yVal !== null && isFinite(yVal) && (!logscale || yVal > 0)) { + yMin = Math.min(yMin, yVal); + yMax = Math.max(yMax, yVal); + } + } + + // Convert Y data to log scale if needed. + // Also, expand the Y range to compress the mini plot a little. + var extraPercent = 0.25; + if (logscale) { + yMax = utils.log10(yMax); + yMax += yMax * extraPercent; + yMin = utils.log10(yMin); + for (i = 0; i < combinedSeries.length; i++) { + combinedSeries[i][1] = utils.log10(combinedSeries[i][1]); + } + } else { + var yExtra; + var yRange = yMax - yMin; + if (yRange <= Number.MIN_VALUE) { + yExtra = yMax * extraPercent; + } else { + yExtra = yRange * extraPercent; + } + yMax += yExtra; + yMin -= yExtra; + } + + return { data: combinedSeries, yMin: yMin, yMax: yMax }; +}; + +/** + * @private + * Places the zoom handles in the proper position based on the current X data window. + */ +rangeSelector.prototype.placeZoomHandles_ = function () { + var xExtremes = this.dygraph_.xAxisExtremes(); + var xWindowLimits = this.dygraph_.xAxisRange(); + var xRange = xExtremes[1] - xExtremes[0]; + var leftPercent = Math.max(0, (xWindowLimits[0] - xExtremes[0]) / xRange); + var rightPercent = Math.max(0, (xExtremes[1] - xWindowLimits[1]) / xRange); + var leftCoord = this.canvasRect_.x + this.canvasRect_.w * leftPercent; + var rightCoord = this.canvasRect_.x + this.canvasRect_.w * (1 - rightPercent); + var handleTop = Math.max(this.canvasRect_.y, this.canvasRect_.y + (this.canvasRect_.h - this.leftZoomHandle_.height) / 2); + var halfHandleWidth = this.leftZoomHandle_.width / 2; + this.leftZoomHandle_.style.left = leftCoord - halfHandleWidth + 'px'; + this.leftZoomHandle_.style.top = handleTop + 'px'; + this.rightZoomHandle_.style.left = rightCoord - halfHandleWidth + 'px'; + this.rightZoomHandle_.style.top = this.leftZoomHandle_.style.top; + + this.leftZoomHandle_.style.visibility = 'visible'; + this.rightZoomHandle_.style.visibility = 'visible'; +}; + +/** + * @private + * Draws the interactive layer in the foreground canvas. + */ +rangeSelector.prototype.drawInteractiveLayer_ = function () { + var ctx = this.fgcanvas_ctx_; + ctx.clearRect(0, 0, this.canvasRect_.w, this.canvasRect_.h); + var margin = 1; + var width = this.canvasRect_.w - margin; + var height = this.canvasRect_.h - margin; + var zoomHandleStatus = this.getZoomHandleStatus_(); + + ctx.strokeStyle = this.getOption_('rangeSelectorForegroundStrokeColor'); + ctx.lineWidth = this.getOption_('rangeSelectorForegroundLineWidth'); + if (!zoomHandleStatus.isZoomed) { + ctx.beginPath(); + ctx.moveTo(margin, margin); + ctx.lineTo(margin, height); + ctx.lineTo(width, height); + ctx.lineTo(width, margin); + ctx.stroke(); + } else { + var leftHandleCanvasPos = Math.max(margin, zoomHandleStatus.leftHandlePos - this.canvasRect_.x); + var rightHandleCanvasPos = Math.min(width, zoomHandleStatus.rightHandlePos - this.canvasRect_.x); + + ctx.fillStyle = 'rgba(240, 240, 240, ' + this.getOption_('rangeSelectorAlpha').toString() + ')'; + ctx.fillRect(0, 0, leftHandleCanvasPos, this.canvasRect_.h); + ctx.fillRect(rightHandleCanvasPos, 0, this.canvasRect_.w - rightHandleCanvasPos, this.canvasRect_.h); + + ctx.beginPath(); + ctx.moveTo(margin, margin); + ctx.lineTo(leftHandleCanvasPos, margin); + ctx.lineTo(leftHandleCanvasPos, height); + ctx.lineTo(rightHandleCanvasPos, height); + ctx.lineTo(rightHandleCanvasPos, margin); + ctx.lineTo(width, margin); + ctx.stroke(); + } +}; + +/** + * @private + * Returns the current zoom handle position information. + * @return {Object} The zoom handle status. + */ +rangeSelector.prototype.getZoomHandleStatus_ = function () { + var halfHandleWidth = this.leftZoomHandle_.width / 2; + var leftHandlePos = parseFloat(this.leftZoomHandle_.style.left) + halfHandleWidth; + var rightHandlePos = parseFloat(this.rightZoomHandle_.style.left) + halfHandleWidth; + return { + leftHandlePos: leftHandlePos, + rightHandlePos: rightHandlePos, + isZoomed: leftHandlePos - 1 > this.canvasRect_.x || rightHandlePos + 1 < this.canvasRect_.x + this.canvasRect_.w + }; +}; + +exports['default'] = rangeSelector; +module.exports = exports['default']; + +},{"../dygraph-interaction-model":12,"../dygraph-utils":17,"../iframe-tarp":19}]},{},[18])(18) +}); +//# sourceMappingURL=dygraph.js.map diff -Nru knot-resolver-5.1.1/debian/NEWS knot-resolver-5.2.1/debian/NEWS --- knot-resolver-5.1.1/debian/NEWS 2020-02-28 09:10:07.000000000 +0000 +++ knot-resolver-5.2.1/debian/NEWS 2020-12-10 14:03:41.000000000 +0000 @@ -1,3 +1,20 @@ +knot-resolver (5.1.2-1) unstable; urgency=medium + + Up to Knot Resolver 4.x, network interface bindings and service + start were done by default via systemd sockets. These systemd sockets + are no longer supported, and upgrading from a 3.x version (as in Debian + Buster) requires manual action to (re)enable the service. + + Please refer to kresd.systemd(7) and to the + /usr/share/doc/knot-resolver/upgrading.html file (from + knot-resolver-doc). An online version of the latter is available at + https://knot-resolver.readthedocs.io/en/stable/upgrading.html#x-to-5-x + + For convenience, a suggested networking configuration can be found in + the file /var/lib/knot-resolver/.upgrade-4-to-5/kresd.conf.net + + -- Santiago Ruano Rincón Tue, 28 Jul 2020 14:48:09 +0200 + knot-resolver (2.0.0-1) unstable; urgency=medium Knot Resolver systemd service units are now templated, so that multiple diff -Nru knot-resolver-5.1.1/debian/not-installed knot-resolver-5.2.1/debian/not-installed --- knot-resolver-5.1.1/debian/not-installed 2020-02-28 09:10:07.000000000 +0000 +++ knot-resolver-5.2.1/debian/not-installed 2020-12-10 14:03:41.000000000 +0000 @@ -6,3 +6,4 @@ usr/share/doc/knot-resolver/COPYING usr/share/doc/knot-resolver/NEWS usr/share/doc/knot-resolver/html/.buildinfo +usr/lib/sysusers.d/knot-resolver.conf diff -Nru knot-resolver-5.1.1/debian/po/de.po knot-resolver-5.2.1/debian/po/de.po --- knot-resolver-5.1.1/debian/po/de.po 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/debian/po/de.po 2020-12-10 14:03:41.000000000 +0000 @@ -0,0 +1,69 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Helge Kreutzmann , 2020. +# +msgid "" +msgstr "" +"Project-Id-Version: knot-resolver 5.1.2-3\n" +"Report-Msgid-Bugs-To: knot-resolver@packages.debian.org\n" +"POT-Creation-Date: 2020-09-14 11:52+0200\n" +"PO-Revision-Date: 2020-08-29 20:33+0200\n" +"Last-Translator: Helge Kreutzmann \n" +"Language-Team: German \n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "Upgrading from Knot Resolver < 5.x" +msgstr "Upgrade von Knot Resolver < 5.x" + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "Knot Resolver configuration file requires manual upgrade." +msgstr "Konfigurationsdatei von Knot Resolver benötigt manuelles Upgrade" + +# FIXME: Up to → Before? +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "" +"Up to Knot Resolver 4.x, network interface bindings and service start were " +"done by default via systemd sockets. These systemd sockets are no longer " +"supported, and upgrading from a 3.x version (as in Debian Buster) requires " +"manual action to (re)enable the service." +msgstr "" +"Bis Knot Resolver 4.x erfolgte die Anbindung der Netzwerkschnittstellen und " +"das Starten des Dienstes standardmäßig über Systemd-Sockets. Diese Systemd-" +"Sockets werden nicht mehr unterstützt und ein Upgrade von einer 3.x-Version " +"(wie in Debian Buster) benötigt einen manuellen Eingriff, um den Dienst " +"(wieder) zu aktivieren." + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "" +"Please refer to kresd.systemd(7) and to the /usr/share/doc/knot-resolver/" +"upgrading.html file (from knot-resolver-doc). An online version of the " +"latter is available at https://knot-resolver.readthedocs.io/en/stable/" +"upgrading.html#x-to-5-x" +msgstr "" +"Bitte lesen Sie kresd.systemd(7) und die Datei /usr/share/doc/knot-resolver/" +"upgrading.html (aus knot-resolver-doc). Eine Online-Version Letzterer ist " +"unter https://knot-resolver.readthedocs.io/en/stable/upgrading.html#x-to-5-x " +"verfügbar." + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "" +"For convenience, a suggested networking configuration can be found in the " +"file /var/lib/knot-resolver/.upgrade-4-to-5/kresd.conf.net" +msgstr "" +"Der Einfachheit halber kann eine vorgeschlagene Netzwerkkonfiguration in der " +"Datei /var/lib/knot-resolver/.upgrade-4-to-5/kresd.conf.net gefunden werden." diff -Nru knot-resolver-5.1.1/debian/po/fr.po knot-resolver-5.2.1/debian/po/fr.po --- knot-resolver-5.1.1/debian/po/fr.po 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/debian/po/fr.po 2020-12-10 14:03:41.000000000 +0000 @@ -0,0 +1,73 @@ +# Translation of knote-resolver debconf templates to french. +# Copyright (C) 2020, French l10n team +# This file is distributed under the same license as the knote-resolver package. +# +# Jean-Pierre Giraud , 2020. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: knot-resolver@packages.debian.org\n" +"POT-Creation-Date: 2020-09-14 11:52+0200\n" +"PO-Revision-Date: 2020-08-22 12:50+0100\n" +"Last-Translator: Jean-Pierre Giraud \n" +"Language-Team: French \n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: Lokalize 2.0\n" + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "Upgrading from Knot Resolver < 5.x" +msgstr "Mise à niveau à partir de Knot < 5.x" + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "Knot Resolver configuration file requires manual upgrade." +msgstr "" +"Le fichier de configuration de Knot Resolver requiert une mise à niveau " +"manuelle." + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "" +"Up to Knot Resolver 4.x, network interface bindings and service start were " +"done by default via systemd sockets. These systemd sockets are no longer " +"supported, and upgrading from a 3.x version (as in Debian Buster) requires " +"manual action to (re)enable the service." +msgstr "" +"Jusqu'à Knot Resolver 4.x, les liaisons avec l'interface réseau et le " +"démarrage du service étaient réalisés par défaut au moyen de sockets de " +"systemd. Ces sockets de systemd ne sont plus pris en charge et la mise à " +"niveau à partir d'une version 3.x (comme dans Debian Buster) nécessite une " +"action manuelle pour (ré)activer le service." + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "" +"Please refer to kresd.systemd(7) and to the /usr/share/doc/knot-resolver/" +"upgrading.html file (from knot-resolver-doc). An online version of the " +"latter is available at https://knot-resolver.readthedocs.io/en/stable/" +"upgrading.html#x-to-5-x" +msgstr "" +"Veuillez consulter kresd.systemd(7) et le fichier /usr/share/doc/knot-" +"resolver/upgrading.html (dans knot-resolver-doc). Une version en ligne de ce " +"fichier est disponible à l'adresse https://knot-resolver.readthedocs.io/en/" +"stable/upgrading.html#x-to-5-x ." + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "" +"For convenience, a suggested networking configuration can be found in the " +"file /var/lib/knot-resolver/.upgrade-4-to-5/kresd.conf.net" +msgstr "" +"Pour des raisons de commodité, une suggestion de configuration de réseau est " +"disponible dans le fichier /var/lib/knot-resolver/.upgrade-4-to-5/kresd.conf." +"net ." diff -Nru knot-resolver-5.1.1/debian/po/nl.po knot-resolver-5.2.1/debian/po/nl.po --- knot-resolver-5.1.1/debian/po/nl.po 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/debian/po/nl.po 2020-12-10 14:03:41.000000000 +0000 @@ -0,0 +1,71 @@ +# Dutch translation of po-debconf messages for knot-resolver. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the knot-resolver package. +# Frans Spiesschaert , 2020. +# +msgid "" +msgstr "" +"Project-Id-Version: knot-resolver_5.1.2-2\n" +"Report-Msgid-Bugs-To: knot-resolver@packages.debian.org\n" +"POT-Creation-Date: 2020-09-14 11:52+0200\n" +"PO-Revision-Date: 2020-08-11 23:00+0200\n" +"Last-Translator: Frans Spiesschaert \n" +"Language-Team: Debian Dutch l10n Team \n" +"Language: nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Gtranslator 3.30.1\n" + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "Upgrading from Knot Resolver < 5.x" +msgstr "Er vindt een opwaardering van Knot Resolver < 5.x plaats" + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "Knot Resolver configuration file requires manual upgrade." +msgstr "" +"Het configuratiebestand van Knot Resolver heeft een handmatige opwaardering " +"nodig." + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "" +"Up to Knot Resolver 4.x, network interface bindings and service start were " +"done by default via systemd sockets. These systemd sockets are no longer " +"supported, and upgrading from a 3.x version (as in Debian Buster) requires " +"manual action to (re)enable the service." +msgstr "" +"Tot Knot Resolver 4.x werden netwerkinterfacebindingen en het starten van de " +"service standaard uitgevoerd via systemd-sockets. Deze systemd-sockets " +"worden niet langer ondersteund en een opwaardering vanaf een 3.x versie " +"(zoals in Debian Buster) vereist een handmatige interventie om de dienst " +"(opnieuw) te activeren." + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "" +"Please refer to kresd.systemd(7) and to the /usr/share/doc/knot-resolver/" +"upgrading.html file (from knot-resolver-doc). An online version of the " +"latter is available at https://knot-resolver.readthedocs.io/en/stable/" +"upgrading.html#x-to-5-x" +msgstr "" +"Raadpleeg kresd.systemd(7) en het bestand /usr/share/doc/knot-resolver/" +"upgrading.html (uit knot-resolver-doc). Een onlineversie van dit laatste is " +"beschikbaar op https://knot-resolver.readthedocs.io/en/stable/upgrading." +"html#x-to-5-x" + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "" +"For convenience, a suggested networking configuration can be found in the " +"file /var/lib/knot-resolver/.upgrade-4-to-5/kresd.conf.net" +msgstr "" +"Om het u makkelijker te maken, is een voorgestelde netwerkconfiguratie te " +"vinden in het bestand /var/lib/knot-resolver/.upgrade-4-to-5/kresd.conf.net" diff -Nru knot-resolver-5.1.1/debian/po/POTFILES.in knot-resolver-5.2.1/debian/po/POTFILES.in --- knot-resolver-5.1.1/debian/po/POTFILES.in 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/debian/po/POTFILES.in 2020-12-10 14:03:41.000000000 +0000 @@ -0,0 +1 @@ +[type: gettext/rfc822deb] knot-resolver.templates diff -Nru knot-resolver-5.1.1/debian/po/templates.pot knot-resolver-5.2.1/debian/po/templates.pot --- knot-resolver-5.1.1/debian/po/templates.pot 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/debian/po/templates.pot 2020-12-10 14:03:41.000000000 +0000 @@ -0,0 +1,58 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the knot-resolver package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: knot-resolver\n" +"Report-Msgid-Bugs-To: knot-resolver@packages.debian.org\n" +"POT-Creation-Date: 2020-09-14 11:52+0200\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: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "Upgrading from Knot Resolver < 5.x" +msgstr "" + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "Knot Resolver configuration file requires manual upgrade." +msgstr "" + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "" +"Up to Knot Resolver 4.x, network interface bindings and service start were " +"done by default via systemd sockets. These systemd sockets are no longer " +"supported, and upgrading from a 3.x version (as in Debian Buster) requires " +"manual action to (re)enable the service." +msgstr "" + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "" +"Please refer to kresd.systemd(7) and to the /usr/share/doc/knot-resolver/" +"upgrading.html file (from knot-resolver-doc). An online version of the " +"latter is available at https://knot-resolver.readthedocs.io/en/stable/" +"upgrading.html#x-to-5-x" +msgstr "" + +#. Type: note +#. Description +#: ../knot-resolver.templates:1001 +msgid "" +"For convenience, a suggested networking configuration can be found in the " +"file /var/lib/knot-resolver/.upgrade-4-to-5/kresd.conf.net" +msgstr "" diff -Nru knot-resolver-5.1.1/debian/rules knot-resolver-5.2.1/debian/rules --- knot-resolver-5.1.1/debian/rules 2020-02-28 09:10:07.000000000 +0000 +++ knot-resolver-5.2.1/debian/rules 2020-12-10 14:03:41.000000000 +0000 @@ -1,7 +1,7 @@ #!/usr/bin/make -f # see FEATURE AREAS in dpkg-buildflags(1) -export DEB_BUILD_MAINT_OPTIONS = hardening=+all,-pie +export DEB_BUILD_MAINT_OPTIONS = hardening=+all # see ENVIRONMENT in dpkg-buildflags(1) # package maintainers to append CFLAGS @@ -13,6 +13,12 @@ DPKG_EXPORT_BUILDFLAGS = 1 include /usr/share/dpkg/default.mk +ifneq (,$(findstring arm64,$(DEB_TARGET_ARCH))) + CONFIG_TESTS=disabled +else + CONFIG_TESTS=enabled +endif + %: dh $@ @@ -29,13 +35,17 @@ -Ddoc=enabled \ -Ddnstap=enabled \ -Dunit_tests=enabled \ - -Dconfig_tests=enabled \ + -Dconfig_tests=$(CONFIG_TESTS) \ -Dextra_tests=disabled override_dh_auto_build: dh_auto_build -- doc dh_auto_build +override_dh_auto_install: + sed -e's/@DEB_HOST_MULTIARCH@/$(DEB_HOST_MULTIARCH)/g' debian/knot-resolver.postinst + dh_auto_install + override_dh_installinit: dh_installinit -pknot-resolver --name=kresd --no-start diff -Nru knot-resolver-5.1.1/debian/salsa-ci.yml knot-resolver-5.2.1/debian/salsa-ci.yml --- knot-resolver-5.1.1/debian/salsa-ci.yml 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/debian/salsa-ci.yml 2020-12-10 14:03:41.000000000 +0000 @@ -0,0 +1,4 @@ +--- +include: + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml diff -Nru knot-resolver-5.1.1/debian/source/lintian-overrides knot-resolver-5.2.1/debian/source/lintian-overrides --- knot-resolver-5.1.1/debian/source/lintian-overrides 2020-02-28 09:10:07.000000000 +0000 +++ knot-resolver-5.2.1/debian/source/lintian-overrides 2020-12-14 16:48:20.000000000 +0000 @@ -1,5 +1,9 @@ # the lines in epoch.js are just human-written long lines: knot-resolver source: source-contains-prebuilt-javascript-object debian/missing-sources/epoch.js line length is 287 characters (>256) +knot-resolver source: source-is-missing debian/missing-sources/epoch.js line length is 287 characters (>256) # the style line in datamaps.world.js is an ugly one-liner but still human-modifiable -knot-resolver source: insane-line-length-in-source-file debian/missing-sources/datamaps.world.js line length is 832 characters (>512) +knot-resolver source: very-long-line-length-in-source-file debian/missing-sources/datamaps.world.js line length is 832 characters (>512) knot-resolver source: source-contains-prebuilt-javascript-object debian/missing-sources/datamaps.world.js line length is 826 characters (>512) +knot-resolver source: source-is-missing debian/missing-sources/datamaps.world.js line length is 826 characters (>512) +# same for dygraph.js +knot-resolver source: source-is-missing debian/missing-sources/dygraph.js line length is 847 characters (>512) diff -Nru knot-resolver-5.1.1/debian/tests/control knot-resolver-5.2.1/debian/tests/control --- knot-resolver-5.1.1/debian/tests/control 2020-06-30 22:28:40.000000000 +0000 +++ knot-resolver-5.2.1/debian/tests/control 2020-12-10 14:03:41.000000000 +0000 @@ -3,4 +3,4 @@ knot-dnsutils, knot-resolver, socat -Restrictions: needs-root +Restrictions: needs-root, skip-not-installable diff -Nru knot-resolver-5.1.1/distro/arch/install knot-resolver-5.2.1/distro/arch/install --- knot-resolver-5.1.1/distro/arch/install 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/arch/install 1970-01-01 00:00:00.000000000 +0000 @@ -1,52 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -post_install() { - getent group knot-resolver &>/dev/null || groupadd -r knot-resolver >/dev/null - getent passwd knot-resolver &>/dev/null || useradd -r -g knot-resolver -d /dev/null -s /bin/false -c knot-resolver knot-resolver >/dev/null - chown -R root:knot-resolver /etc/knot-resolver -} - -pre_upgrade() { - # upgrade from 4.x to 5.x - save socket interfaces before package uninstall - if [ -f /usr/lib/systemd/system/kresd.socket ] ; then - export UPG_DIR=/var/lib/knot-resolver/.upgrade-4-to-5 - mkdir -p ${UPG_DIR} - touch ${UPG_DIR}/.unfinished - - for sock in kresd.socket kresd-tls.socket kresd-webmgmt.socket kresd-doh.socket ; do - if systemctl is-enabled ${sock} 2>/dev/null | grep -qv masked ; then - systemctl show ${sock} -p Listen > ${UPG_DIR}/${sock} - case "$(systemctl show ${sock} -p BindIPv6Only)" in - *ipv6-only) - touch ${UPG_DIR}/${sock}.v6only - ;; - *default) - if cat /proc/sys/net/ipv6/bindv6only | grep -q 1 ; then - touch ${UPG_DIR}/${sock}.v6only - fi - ;; - esac - fi - done - fi -} - -post_upgrade() { - # upgrade from 4.x to 5.x - convert systemd socket configs to net.listen() format - export UPG_DIR=/var/lib/knot-resolver/.upgrade-4-to-5 - if [ -f ${UPG_DIR}/.unfinished ] ; then - rm -f ${UPG_DIR}/.unfinished - kresd -c /usr/lib/knot-resolver/upgrade-4-to-5.lua &>/dev/null - echo -e "\n !!! WARNING !!!" - echo -e "Knot Resolver configuration file requires manual upgrade.\n" - cat ${UPG_DIR}/kresd.conf.net 2>/dev/null - fi - - chown -R root:knot-resolver /etc/knot-resolver - chown -R root:knot-resolver /var/lib/knot-resolver -} - -post_remove() { - getent passwd knot-resolver &>/dev/null && userdel knot-resolver >/dev/null - getent group knot-resolver &>/dev/null && groupdel knot-resolver >/dev/null - true -} diff -Nru knot-resolver-5.1.1/distro/arch/PKGBUILD knot-resolver-5.2.1/distro/arch/PKGBUILD --- knot-resolver-5.1.1/distro/arch/PKGBUILD 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/arch/PKGBUILD 2020-12-09 09:44:29.000000000 +0000 @@ -5,62 +5,60 @@ pkgname=knot-resolver pkgver=__VERSION__ pkgrel=1 -pkgdesc='full caching DNS resolver implementation' -url='https://www.knot-resolver.cz/' +pkgdesc='Caching DNSSEC-validating DNS resolver' arch=('x86_64' 'armv7h') +url='https://www.knot-resolver.cz/' license=('GPL3') -backup=('etc/knot-resolver/kresd.conf') -options=(debug strip) -install=install depends=( 'dnssec-anchors' 'gnutls' - 'knot>=2.8' + 'knot' 'libedit' 'libuv' 'lmdb' 'luajit' 'systemd' 'libcap-ng' -) -optdepends=( - 'lua51-basexx: experimental_dot_auth module', - 'lua51-cqueues: http and dns64 module, policy.rpz() function', - 'lua51-http: http and prefill modules, trust_anchors bootstrap', - 'lua51-psl: policy.slice_randomize_psl() function', + 'libnghttp2' ) makedepends=( 'cmocka' 'meson' 'systemd-libs' ) - +optdepends=( + 'lua51-basexx: experimental_dot_auth module' + 'lua51-cqueues: http and dns64 module, policy.rpz() function' + 'lua51-http: http and prefill modules, trust_anchors bootstrap' + 'lua51-psl: policy.slice_randomize_psl() function' +) +backup=('etc/knot-resolver/kresd.conf') +options=(debug strip) source=("knot-resolver_${pkgver}.orig.tar.xz") - sha256sums=('SKIP') build() { cd "${srcdir}/${pkgname}-${pkgver}" - meson build_arch \ + meson build \ --buildtype=release \ --prefix=/usr \ --sbindir=bin \ - -Dkeyfile_default=/etc/trusted-key.key \ - -Dsystemd_files=enabled \ - -Dclient=enabled \ - -Dinstall_kresd_conf=enabled \ - -Dunit_tests=enabled - ninja -C build_arch + -D keyfile_default=/etc/trusted-key.key \ + -D systemd_files=enabled \ + -D client=enabled \ + -D install_kresd_conf=enabled \ + -D unit_tests=enabled + ninja -C build } check() { cd "${srcdir}/${pkgname}-${pkgver}" - meson test -C build_arch + meson test -C build } package() { cd "${srcdir}/${pkgname}-${pkgver}" - DESTDIR=${pkgdir} ninja -C build_arch install + DESTDIR=${pkgdir} ninja -C build install # add kresd.target to multi-user.target.wants to support enabling kresd services install -d -m 0755 "${pkgdir}/usr/lib/systemd/system/multi-user.target.wants" diff -Nru knot-resolver-5.1.1/distro/deb/control knot-resolver-5.2.1/distro/deb/control --- knot-resolver-5.1.1/distro/deb/control 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/deb/control 2020-12-09 09:44:29.000000000 +0000 @@ -12,6 +12,7 @@ libknot-dev (>= 2.8), liblmdb-dev, libluajit-5.1-dev, + libnghttp2-dev, libsystemd-dev (>= 227) [linux-any], libcap-ng-dev, libuv1-dev, @@ -22,6 +23,7 @@ python3-breathe, python3-sphinx, python3-sphinx-rtd-theme, + texinfo, libssl-dev, Homepage: https://www.knot-resolver.cz/ @@ -87,7 +89,7 @@ ${shlibs:Depends}, Breaks: knot-resolver-module-tinyweb (<< 1.1.0~git20160713-1~), -Description: HTTP/2 module for Knot Resolver +Description: HTTP module for Knot Resolver The Knot Resolver is a caching full resolver implementation written in C and LuaJIT, including both a resolver library and a daemon. Modular architecture of the library keeps the core tiny and diff -Nru knot-resolver-5.1.1/distro/deb/knot-resolver-doc.info knot-resolver-5.2.1/distro/deb/knot-resolver-doc.info --- knot-resolver-5.1.1/distro/deb/knot-resolver-doc.info 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/distro/deb/knot-resolver-doc.info 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,2 @@ +debian/tmp/usr/share/info/knot-resolver.info +debian/tmp/usr/share/info/knot-resolver-figures/* diff -Nru knot-resolver-5.1.1/distro/deb/not-installed knot-resolver-5.2.1/distro/deb/not-installed --- knot-resolver-5.1.1/distro/deb/not-installed 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/deb/not-installed 2020-12-09 09:44:29.000000000 +0000 @@ -4,3 +4,4 @@ usr/include/libkres/*.h usr/lib/*.so usr/lib/pkgconfig/libkres.pc +usr/lib/sysusers.d/knot-resolver.conf diff -Nru knot-resolver-5.1.1/distro/rpm/knot-resolver.spec knot-resolver-5.2.1/distro/rpm/knot-resolver.spec --- knot-resolver-5.1.1/distro/rpm/knot-resolver.spec 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/rpm/knot-resolver.spec 2020-12-09 09:44:29.000000000 +0000 @@ -13,7 +13,7 @@ Release: 1%{?dist} Summary: Caching full DNS Resolver -License: GPLv3 +License: GPL-3.0-or-later URL: https://www.knot-resolver.cz/ Source0: knot-resolver_%{version}.orig.tar.xz @@ -45,6 +45,7 @@ BuildRequires: pkgconfig(libknot) >= 2.8 BuildRequires: pkgconfig(libzscanner) >= 2.8 BuildRequires: pkgconfig(libdnssec) >= 2.8 +BuildRequires: pkgconfig(libnghttp2) BuildRequires: pkgconfig(libsystemd) BuildRequires: pkgconfig(libcap-ng) BuildRequires: pkgconfig(libuv) @@ -92,6 +93,7 @@ BuildRequires: doxygen BuildRequires: python3-breathe BuildRequires: python3-sphinx_rtd_theme +BuildRequires: texinfo %endif %description @@ -123,7 +125,7 @@ %if "x%{?suse_version}" == "x" %package module-http -Summary: HTTP/2 module for Knot Resolver +Summary: HTTP module for Knot Resolver Requires: %{name} = %{version}-%{release} %if 0%{?fedora} || 0%{?rhel} > 7 Requires: lua5.1-http @@ -134,9 +136,10 @@ %endif %description module-http -HTTP/2 module for Knot Resolver has multiple uses. It enables use of -DNS-over-HTTP, can serve as API endpoint for other modules or provide a web -interface for local visualization of the resolver cache and queries. +HTTP module for Knot Resolver can serve as API endpoint for other modules or +provide a web interface for local visualization of the resolver cache and +queries. It can also serve DNS-over-HTTPS, but it is deprecated in favor of +native C implementation, which doesn't require this package. %endif %prep @@ -185,6 +188,9 @@ # remove modules with missing dependencies rm %{buildroot}%{_libdir}/knot-resolver/kres_modules/etcd.lua +# remove unused sysusers +rm %{buildroot}%{_prefix}/lib/sysusers.d/knot-resolver.conf + %if 0%{?suse_version} rm %{buildroot}%{_libdir}/knot-resolver/kres_modules/experimental_dot_auth.lua rm -r %{buildroot}%{_libdir}/knot-resolver/kres_modules/http @@ -269,12 +275,12 @@ %doc %{_pkgdocdir}/AUTHORS %doc %{_pkgdocdir}/NEWS %doc %{_pkgdocdir}/examples -%attr(755,root,knot-resolver) %dir %{_sysconfdir}/knot-resolver -%attr(644,root,knot-resolver) %config(noreplace) %{_sysconfdir}/knot-resolver/kresd.conf -%attr(644,root,knot-resolver) %config(noreplace) %{_sysconfdir}/knot-resolver/root.hints -%attr(644,root,knot-resolver) %{_sysconfdir}/knot-resolver/icann-ca.pem -%attr(775,root,knot-resolver) %dir %{_sharedstatedir}/knot-resolver -%attr(664,root,knot-resolver) %{_sharedstatedir}/knot-resolver/root.keys +%dir %{_sysconfdir}/knot-resolver +%config(noreplace) %{_sysconfdir}/knot-resolver/kresd.conf +%config(noreplace) %{_sysconfdir}/knot-resolver/root.hints +%{_sysconfdir}/knot-resolver/icann-ca.pem +%attr(750,knot-resolver,knot-resolver) %dir %{_sharedstatedir}/knot-resolver +%attr(640,knot-resolver,knot-resolver) %{_sharedstatedir}/knot-resolver/root.keys %{_unitdir}/kresd@.service %{_unitdir}/kres-cache-gc.service %{_unitdir}/kresd.target @@ -284,7 +290,7 @@ %{_tmpfilesdir}/knot-resolver.conf %ghost /run/%{name} %ghost %{_localstatedir}/cache/%{name} -%attr(770,root,knot-resolver) %dir %{_libdir}/%{name} +%attr(750,knot-resolver,knot-resolver) %dir %{_libdir}/%{name} %{_sbindir}/kresd %{_sbindir}/kresc %{_sbindir}/kres-cache-gc @@ -327,6 +333,9 @@ %files doc %dir %{_pkgdocdir} %doc %{_pkgdocdir}/html +%doc %{_datadir}/info/knot-resolver.info* +%dir %{_datadir}/info/knot-resolver-figures +%doc %{_datadir}/info/knot-resolver-figures/* %endif %if "x%{?suse_version}" == "x" diff -Nru knot-resolver-5.1.1/distro/tests/ansible-roles/knot_resolver/tasks/configure_doh2.yaml knot-resolver-5.2.1/distro/tests/ansible-roles/knot_resolver/tasks/configure_doh2.yaml --- knot-resolver-5.1.1/distro/tests/ansible-roles/knot_resolver/tasks/configure_doh2.yaml 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/ansible-roles/knot_resolver/tasks/configure_doh2.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,8 @@ +--- +# SPDX-License-Identifier: GPL-3.0-or-later +- name: doh2_config set up kresd.conf + blockinfile: + marker: -- {mark} ANSIBLE MANAGED BLOCK + block: | + net.listen('127.0.0.1', 44354, { kind = 'doh2' }) + path: /etc/knot-resolver/kresd.conf diff -Nru knot-resolver-5.1.1/distro/tests/ansible-roles/knot_resolver/tasks/main.yaml knot-resolver-5.2.1/distro/tests/ansible-roles/knot_resolver/tasks/main.yaml --- knot-resolver-5.1.1/distro/tests/ansible-roles/knot_resolver/tasks/main.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/ansible-roles/knot_resolver/tasks/main.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -26,7 +26,13 @@ - include: test_kres_cache_gc.yaml - - name: Test DoH + - name: Test DoH (new implementation) + block: + - include: configure_doh2.yaml + - include: restart_kresd.yaml + - include: test_doh2.yaml + + - name: Test DoH (legacy) block: - name: Install knot-resolver-module-http package: diff -Nru knot-resolver-5.1.1/distro/tests/ansible-roles/knot_resolver/tasks/test_dnssec.yaml knot-resolver-5.2.1/distro/tests/ansible-roles/knot_resolver/tasks/test_dnssec.yaml --- knot-resolver-5.1.1/distro/tests/ansible-roles/knot_resolver/tasks/test_dnssec.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/ansible-roles/knot_resolver/tasks/test_dnssec.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -1,15 +1,15 @@ --- # SPDX-License-Identifier: GPL-3.0-or-later -- name: dnssec_test dnssec-failed.org +cd returns NOERROR +- name: dnssec_test bogussig.bad-dnssec.wb.sidnlabs.nl. +cd returns NOERROR tags: - test - shell: kdig +cd @127.0.0.1 dnssec-failed.org + shell: kdig +cd @127.0.0.1 bogussig.bad-dnssec.wb.sidnlabs.nl. register: res failed_when: '"status: NOERROR" not in res.stdout' -- name: dnssec_test dnssec-failed.org returns SERVFAIL +- name: dnssec_test bogussig.bad-dnssec.wb.sidnlabs.nl. returns SERVFAIL tags: - test - shell: kdig @127.0.0.1 dnssec-failed.org + shell: kdig @127.0.0.1 bogussig.bad-dnssec.wb.sidnlabs.nl. register: res failed_when: '"status: SERVFAIL" not in res.stdout' diff -Nru knot-resolver-5.1.1/distro/tests/ansible-roles/knot_resolver/tasks/test_doh2.yaml knot-resolver-5.2.1/distro/tests/ansible-roles/knot_resolver/tasks/test_doh2.yaml --- knot-resolver-5.1.1/distro/tests/ansible-roles/knot_resolver/tasks/test_doh2.yaml 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/ansible-roles/knot_resolver/tasks/test_doh2.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,24 @@ +--- +# SPDX-License-Identifier: GPL-3.0-or-later +- name: doh2_test check kdig https support + shell: kdig --help | grep -q '+\S*https' + register: kdig_https + ignore_errors: true + +- name: doh2_test query localhost. A + # use curl instead of ansible builtins (get_url/uri) + # because they currently use unsupported HTTP/1.1 + shell: | + curl -k -o /tmp/doh_test https://127.0.0.1:44354/doh?dns=1Y0BAAABAAAAAAAACWxvY2FsaG9zdAAAAQAB + echo "e5c2710e6ecb78c089ab608ad5861b87be0d1c623c4d58b4eee3b21c06aa2008 /tmp/doh_test" > /tmp/doh_test.sha256 + sha256sum --check /tmp/doh_test.sha256 + args: + # disable warning about using curl - we know what we're doing + warn: false + when: kdig_https is failed + +- name: doh2_test kdig localhost. A + shell: | + kdig @127.0.0.1 -p 44354 +https nic.cz || exit 1 + kdig @127.0.0.1 -p 44354 +https-get nic.cz || exit 2 + when: kdig_https is succeeded diff -Nru knot-resolver-5.1.1/distro/tests/ansible-roles/knot_resolver/tasks/test_doh.yaml knot-resolver-5.2.1/distro/tests/ansible-roles/knot_resolver/tasks/test_doh.yaml --- knot-resolver-5.1.1/distro/tests/ansible-roles/knot_resolver/tasks/test_doh.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/ansible-roles/knot_resolver/tasks/test_doh.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -5,4 +5,5 @@ url: https://127.0.0.1:44353/doh?dns=1Y0BAAABAAAAAAAACWxvY2FsaG9zdAAAAQAB sha256sum: e5c2710e6ecb78c089ab608ad5861b87be0d1c623c4d58b4eee3b21c06aa2008 dest: /tmp/doh_test + mode: 0644 validate_certs: false diff -Nru knot-resolver-5.1.1/distro/tests/ansible-roles/obs_repos/tasks/CentOS.yaml knot-resolver-5.2.1/distro/tests/ansible-roles/obs_repos/tasks/CentOS.yaml --- knot-resolver-5.1.1/distro/tests/ansible-roles/obs_repos/tasks/CentOS.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/ansible-roles/obs_repos/tasks/CentOS.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -9,4 +9,5 @@ get_url: url: "{{ obs_repofile_url }}" dest: /etc/yum.repos.d/home:CZ-NIC:{{ item }}.repo + mode: 0644 with_items: "{{ repos }}" diff -Nru knot-resolver-5.1.1/distro/tests/ansible-roles/obs_repos/tasks/Debian.yaml knot-resolver-5.2.1/distro/tests/ansible-roles/obs_repos/tasks/Debian.yaml --- knot-resolver-5.1.1/distro/tests/ansible-roles/obs_repos/tasks/Debian.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/ansible-roles/obs_repos/tasks/Debian.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -2,8 +2,9 @@ # SPDX-License-Identifier: GPL-3.0-or-later - name: Add upstream package signing key get_url: - url: https://gitlab.labs.nic.cz/knot/knot-resolver-release/raw/master/cznic-obs.gpg.asc + url: https://gitlab.nic.cz/knot/knot-resolver-release/raw/master/cznic-obs.gpg.asc dest: /etc/apt/trusted.gpg.d/cznic-obs.gpg.asc + mode: 0644 - name: Add OBS repo(s) apt_repository: diff -Nru knot-resolver-5.1.1/distro/tests/ansible-roles/obs_repos/tasks/Fedora.yaml knot-resolver-5.2.1/distro/tests/ansible-roles/obs_repos/tasks/Fedora.yaml --- knot-resolver-5.1.1/distro/tests/ansible-roles/obs_repos/tasks/Fedora.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/ansible-roles/obs_repos/tasks/Fedora.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -4,4 +4,5 @@ get_url: url: "{{ obs_repofile_url }}" dest: "/etc/yum.repos.d/home:CZ-NIC:{{ item }}.repo" + mode: 0644 with_items: "{{ repos }}" diff -Nru knot-resolver-5.1.1/distro/tests/ansible-roles/obs_repos/tasks/Ubuntu.yaml knot-resolver-5.2.1/distro/tests/ansible-roles/obs_repos/tasks/Ubuntu.yaml --- knot-resolver-5.1.1/distro/tests/ansible-roles/obs_repos/tasks/Ubuntu.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/ansible-roles/obs_repos/tasks/Ubuntu.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later - name: Add upstream package signing key apt_key: - url: https://gitlab.labs.nic.cz/knot/knot-resolver-release/raw/master/cznic-obs.gpg.asc + url: https://gitlab.nic.cz/knot/knot-resolver-release/raw/master/cznic-obs.gpg.asc state: present - name: Add OBS repo(s) diff -Nru knot-resolver-5.1.1/distro/tests/fedora30/ansible.cfg knot-resolver-5.2.1/distro/tests/fedora30/ansible.cfg --- knot-resolver-5.1.1/distro/tests/fedora30/ansible.cfg 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/fedora30/ansible.cfg 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later - -[defaults] - -# additional paths to search for roles in, colon separated -roles_path = ../ansible-roles -interpreter_python = auto -stdout_callback=debug diff -Nru knot-resolver-5.1.1/distro/tests/fedora30/Vagrantfile knot-resolver-5.2.1/distro/tests/fedora30/Vagrantfile --- knot-resolver-5.1.1/distro/tests/fedora30/Vagrantfile 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/fedora30/Vagrantfile 1970-01-01 00:00:00.000000000 +0000 @@ -1,30 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -# -*- mode: ruby -*- -# vi: set ft=ruby : -# - -Vagrant.configure(2) do |config| - - config.vm.box = "fedora/30-cloud-base" - config.vm.synced_folder ".", "/vagrant", disabled: true - - config.vm.define "fedora30_knot-resolver" do |machine| - machine.vm.provision "ansible" do |ansible| - ansible.playbook = "../knot-resolver-pkgtest.yaml" - ansible.extra_vars = { - ansible_python_interpreter: "/usr/bin/python3", - } - end - end - - config.vm.provider :libvirt do |libvirt| - libvirt.cpus = 1 - libvirt.memory = 1024 - end - - config.vm.provider :virtualbox do |vbox| - vbox.cpus = 1 - vbox.memory = 1024 - end - -end diff -Nru knot-resolver-5.1.1/distro/tests/fedora31/ansible.cfg knot-resolver-5.2.1/distro/tests/fedora31/ansible.cfg --- knot-resolver-5.1.1/distro/tests/fedora31/ansible.cfg 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/fedora31/ansible.cfg 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later - -[defaults] - -# additional paths to search for roles in, colon separated -roles_path = ../ansible-roles -interpreter_python = auto -stdout_callback=debug diff -Nru knot-resolver-5.1.1/distro/tests/fedora31/Vagrantfile knot-resolver-5.2.1/distro/tests/fedora31/Vagrantfile --- knot-resolver-5.1.1/distro/tests/fedora31/Vagrantfile 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/fedora31/Vagrantfile 1970-01-01 00:00:00.000000000 +0000 @@ -1,30 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -# -*- mode: ruby -*- -# vi: set ft=ruby : -# - -Vagrant.configure(2) do |config| - - config.vm.box = "fedora/31-cloud-base" - config.vm.synced_folder ".", "/vagrant", disabled: true - - config.vm.define "fedora31_knot-resolver" do |machine| - machine.vm.provision "ansible" do |ansible| - ansible.playbook = "../knot-resolver-pkgtest.yaml" - ansible.extra_vars = { - ansible_python_interpreter: "/usr/bin/python3", - } - end - end - - config.vm.provider :libvirt do |libvirt| - libvirt.cpus = 1 - libvirt.memory = 1024 - end - - config.vm.provider :virtualbox do |vbox| - vbox.cpus = 1 - vbox.memory = 1024 - end - -end diff -Nru knot-resolver-5.1.1/distro/tests/fedora33/ansible.cfg knot-resolver-5.2.1/distro/tests/fedora33/ansible.cfg --- knot-resolver-5.1.1/distro/tests/fedora33/ansible.cfg 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/fedora33/ansible.cfg 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +[defaults] + +# additional paths to search for roles in, colon separated +roles_path = ../ansible-roles +interpreter_python = auto +stdout_callback=debug diff -Nru knot-resolver-5.1.1/distro/tests/fedora33/Vagrantfile knot-resolver-5.2.1/distro/tests/fedora33/Vagrantfile --- knot-resolver-5.1.1/distro/tests/fedora33/Vagrantfile 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/fedora33/Vagrantfile 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# -*- mode: ruby -*- +# vi: set ft=ruby : +# + +Vagrant.configure(2) do |config| + + config.vm.box = "fedora/33-cloud-base" + config.vm.synced_folder ".", "/vagrant", disabled: true + + config.vm.define "fedora33_knot-resolver" do |machine| + machine.vm.provision "ansible" do |ansible| + ansible.playbook = "../knot-resolver-pkgtest.yaml" + ansible.extra_vars = { + ansible_python_interpreter: "/usr/bin/python3", + } + end + end + + config.vm.provider :libvirt do |libvirt| + libvirt.cpus = 1 + libvirt.memory = 1024 + end + + config.vm.provider :virtualbox do |vbox| + vbox.cpus = 1 + vbox.memory = 1024 + end + +end diff -Nru knot-resolver-5.1.1/distro/tests/ubuntu1910/ansible.cfg knot-resolver-5.2.1/distro/tests/ubuntu1910/ansible.cfg --- knot-resolver-5.1.1/distro/tests/ubuntu1910/ansible.cfg 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/ubuntu1910/ansible.cfg 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later - -[defaults] - -# additional paths to search for roles in, colon separated -roles_path = ../ansible-roles -interpreter_python = auto -stdout_callback=debug diff -Nru knot-resolver-5.1.1/distro/tests/ubuntu1910/Vagrantfile knot-resolver-5.2.1/distro/tests/ubuntu1910/Vagrantfile --- knot-resolver-5.1.1/distro/tests/ubuntu1910/Vagrantfile 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/ubuntu1910/Vagrantfile 1970-01-01 00:00:00.000000000 +0000 @@ -1,31 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -# -*- mode: ruby -*- -# vi: set ft=ruby : -# - -Vagrant.configure(2) do |config| - - # TODO: switch to generic/ubuntu1910 when available (has libvirt box) - config.vm.box = "ubuntu/eoan64" - config.vm.synced_folder ".", "/vagrant", disabled: true - - config.vm.define "ubuntu1910_knot-resolver" do |machine| - machine.vm.provision "ansible" do |ansible| - ansible.playbook = "../knot-resolver-pkgtest.yaml" - ansible.extra_vars = { - ansible_python_interpreter: "/usr/bin/python3" - } - end - end - - config.vm.provider :libvirt do |libvirt| - libvirt.cpus = 1 - libvirt.memory = 1024 - end - - config.vm.provider :virtualbox do |vbox| - vbox.cpus = 1 - vbox.memory = 1024 - end - -end diff -Nru knot-resolver-5.1.1/distro/tests/ubuntu2010/ansible.cfg knot-resolver-5.2.1/distro/tests/ubuntu2010/ansible.cfg --- knot-resolver-5.1.1/distro/tests/ubuntu2010/ansible.cfg 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/ubuntu2010/ansible.cfg 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +[defaults] + +# additional paths to search for roles in, colon separated +roles_path = ../ansible-roles +interpreter_python = auto +stdout_callback=debug diff -Nru knot-resolver-5.1.1/distro/tests/ubuntu2010/Vagrantfile knot-resolver-5.2.1/distro/tests/ubuntu2010/Vagrantfile --- knot-resolver-5.1.1/distro/tests/ubuntu2010/Vagrantfile 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/distro/tests/ubuntu2010/Vagrantfile 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# -*- mode: ruby -*- +# vi: set ft=ruby : +# + +Vagrant.configure(2) do |config| + + # TODO: switch to generic/ubuntu2010 when available (has libvirt box) + config.vm.box = "ubuntu/groovy64" + config.vm.synced_folder ".", "/vagrant", disabled: true + + config.vm.define "ubuntu2010_knot-resolver" do |machine| + machine.vm.provision "ansible" do |ansible| + ansible.playbook = "../knot-resolver-pkgtest.yaml" + ansible.extra_vars = { + ansible_python_interpreter: "/usr/bin/python3" + } + end + end + + config.vm.provider :libvirt do |libvirt| + libvirt.cpus = 1 + libvirt.memory = 1024 + end + + config.vm.provider :virtualbox do |vbox| + vbox.cpus = 1 + vbox.memory = 1024 + end + +end diff -Nru knot-resolver-5.1.1/distro/turris/files/convert_config.sh knot-resolver-5.2.1/distro/turris/files/convert_config.sh --- knot-resolver-5.1.1/distro/turris/files/convert_config.sh 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/turris/files/convert_config.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,19 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-2.0 - -convert_hostname_config(){ - #convert hostname_config from option to list - hostnames=$(uci show resolver.kresd.hostname_config) - item_count=$(echo "$hostnames"| tr -cd "'"|wc -c) - if [ "$item_count" -gt "2" ] || [ "$item_count" == "0" ]; then - echo "resolver.kresd.hostname_config was already converted to list" - else - echo "converting resolver.kresd.hostname_config to list" - val=$(uci get resolver.kresd.hostname_config) - uci delete resolver.kresd.hostname_config - uci add_list resolver.kresd.hostname_config=$val - uci commit resolver - fi -} - -convert_hostname_config diff -Nru knot-resolver-5.1.1/distro/turris/files/kresd.init knot-resolver-5.2.1/distro/turris/files/kresd.init --- knot-resolver-5.1.1/distro/turris/files/kresd.init 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/turris/files/kresd.init 1970-01-01 00:00:00.000000000 +0000 @@ -1,419 +0,0 @@ -#!/bin/sh /etc/rc.common -# SPDX-License-Identifier: GPL-2.0 - -START=61 -STOP=00 - -USE_PROCD=1 -PROG=/usr/bin/kresd -CONFIGFILE=/tmp/kresd.config -ROOTKEYFILE=/etc/root.keys -DEFAULT_RUNDIR=/tmp/kresd -STATIC_DOMAINS=1 -DYNAMIC_DOMAINS=0 -USERNAME=kresd -GROUP=kresd - -# Check the /etc/unbound/root.keys is reasonable and try to fix it with given command -check_root_key() { - # Don't do anything if it looks OK - grep -qE '[[:space:]](DNSKEY|DS|TYPE[[:digit:]][[:digit:]])[[:space:]]' $ROOTKEYFILE && return - # Not OK -> run the command and hope for the best - echo "Trying to restore broken $ROOTKEYFILE with command $@" - eval "$@" -} - -print() { - echo "...---..." -} - -service_triggers() { - procd_add_reload_trigger "dhcp" -} - -add_listen_addr() { - local addr="$1" - local port="$2" - [ "$addr" = "::0" ] && addr="::" - procd_append_param command -a "$addr#$port" -} - -init_header() { - echo "--Automatically generated file; DO NOT EDIT" > $CONFIGFILE - echo "modules = {" >> $CONFIGFILE - config_get_bool prefetch common prefetch 0 - echo " 'hints > iterate'" >> $CONFIGFILE - echo " , 'policy'" >> $CONFIGFILE - if [ "$prefetch" \!= 0 ]; then - echo " , 'stats'" >> $CONFIGFILE - echo " , predict = {" >> $CONFIGFILE - echo " window = 30 -- 30 minutes sampling window" >> $CONFIGFILE - echo " , period = 24*(60/30) -- track last 24 hours" >> $CONFIGFILE - echo " }" >> $CONFIGFILE - fi - echo "}" >> $CONFIGFILE - - echo "hints.use_nodata(true)" >> $CONFIGFILE - - # clear hints file - echo "" > $HINTS_CONFIG -} - -set_param_func() { - local func_name="$1" - local val="$2" - echo "$func_name($val)">>$CONFIGFILE -} - -set_param_var() { - local name="$1" - local val="$2" - echo "$name=$val">>$CONFIGFILE -} - -include_custom_config() { - local custom_config_path - config_get custom_config_path "kresd" include_config - if [ -e "$custom_config_path" ]; then - echo "" >> $CONFIGFILE - echo "--- Included custom configuration file from: ---" >> $CONFIGFILE - echo "--- $custom_config_path " >> $CONFIGFILE - cat $custom_config_path >> $CONFIGFILE - else - [ -z "$custom_config_path" ] || echo "Warning! Kresd custom config file $custom_config_path doesn't exist." - fi -} - -load_uci_config_common() { - local interface keyfile verbose port msg_buffer_size net_ipv4 net_ipv6 msg_cache_size do_forward ignore_root_key keep_cache_bool - local forward_dns - local section="common" - - # port - config_get port "$section" port - - # list of listen addresses - config_list_foreach "$section" interface add_listen_addr $port - - # ignore keyfile - config_get_bool ignore_root_key "$section" ignore_root_key 0 - - if [ "$ignore_root_key" = "0" ]; then - #keyfile - config_get keyfile "$section" keyfile - if [ -n "$keyfile" ]; then - ROOTKEYFILE=$keyfile - check_root_key cert-backup -x $ROOTKEYFILE - procd_append_param command -k "$keyfile" - fi - fi - - # verbosity - config_get verbose "$section" verbose 0 - [ "$verbose" -ge 1 ] && procd_append_param command -v - - # EDNS payload - config_get msg_buffer_size "$section" msg_buffer_size - [ "$msg_buffer_size" -ge 512 ] && set_param_func "net.bufsize" "$msg_buffer_size" - - # ipv4 - config_get_bool net_ipv4 "$section" net_ipv4 - net_ipv4_bool=$(if test "$net_ipv4" -eq "1"; then echo true; else echo false; fi) - set_param_var "net.ipv4" "$net_ipv4_bool" - - # ipv6 - config_get_bool net_ipv6 "$section" net_ipv6 - net_ipv6_bool=$(if test "$net_ipv6" -eq "1"; then echo true; else echo false; fi) - set_param_var "net.ipv6" "$net_ipv6_bool" - - # msg_cache_size - config_get msg_cache_size "$section" msg_cache_size - conv_msg_cache_size=$(echo "$msg_cache_size"|sed -e 's/k$/*kB/gi' -e 's/M$/*MB/gi' -e 's/G$/*GB/gi') - set_param_func "cache.open" "$conv_msg_cache_size" - - # clear cache - config_get_bool keep_cache_bool "kresd" keep_cache 1 - if [ "$keep_cache_bool" = "0" ]; then - set_param_func "cache.clear" "" - fi - - config_get_bool do_forward "$section" forward_upstream 1 - config_get forward_dns "$section" forward_custom - - #check custom forwarding - if [ -n "$forward_dns" ] && [ "$do_forward" = "1" ]; then - dns_config_load "$forward_dns" - else - if [ "$do_forward" = "1" ] ; then - local SERVERS - SERVERS=$(sed -ne 's/^nameserver \(.*\)/\1/p' /tmp/resolv.conf.auto | sort -u |head -n 4) - if [ "$SERVERS" ] ; then - if [ "$ignore_root_key" = "1" ] ; then - echo "policy.add(policy.all(policy.STUB({">>$CONFIGFILE - else - echo "policy.add(policy.all(policy.FORWARD({">>$CONFIGFILE - fi - for SERVER in $SERVERS ; do - echo " '$SERVER',">>$CONFIGFILE - done - echo "})))">>$CONFIGFILE - fi - fi - md5sum /tmp/resolv.conf.auto | cut -f1 -d\ >/tmp/resolv.conf.auto.last.md5 - fi - - # enable static local domains - config_get_bool STATIC_DOMAINS "$section" static_domains 1 - - # get dynamic domains - config_get_bool DYNAMIC_DOMAINS "$section" dynamic_domains 1 - - # include custom kresd config - include_custom_config -} - -get_local_domain() { - config_get DOMAIN $1 local - [ -z "$DOMAIN" ] || DOMAIN="`echo "$DOMAIN" | sed 's|/||g'`" -} - -set_local_host() { - config_get NAME $1 name - config_get IP $1 ip - if [ -n "$NAME" ] && [ -n "$DOMAIN" ] && [ -n "$IP" ]; then - echo "$IP $NAME.$DOMAIN" >> $HINTS_CONFIG - fi -} - -get_dnsmasq_dhcp_script() { - local config="$1" - local DHCPSCRIPT - config_get DHCPSCRIPT "$config" dhcpscript - echo $DHCPSCRIPT -} - -add_rpz_file() { - local rpz_file="$1" - if [ -e "$rpz_file" ]; then - echo "policy.add(policy.rpz(policy.DENY, '$rpz_file'))" >>$CONFIGFILE - fi -} - -add_hostname_config() { - local hostname_config="$1" - if [ -e "$hostname_config" ] && [ "$STATIC_DOMAINS" = "1" ]; then - cat $hostname_config >> $HINTS_CONFIG - fi -} - -modify_rundir() { - local rundir - local section="kresd" - - #rundir - config_get rundir "$section" rundir "$DEFAULT_RUNDIR" - if [ ! -d "$rundir" ]; then - mkdir -p "$rundir" - fi - chown -R $USERNAME:$GROUP "$rundir" #maybe we should be more restrictive ... - DEFAULT_RUNDIR="$rundir" - HINTS_CONFIG=$DEFAULT_RUNDIR/hints.tmp -} - -load_uci_config_kresd() { - local addr config keyfile forks verbose log_stderr log_stdout hostname_config - local section="kresd" - - # knot-resolver config - procd_append_param command -c "$CONFIGFILE" - - # number of forks - config_get forks "$section" forks 1 - procd_append_param command -f "$forks" - - # rundir - procd_append_param command "$DEFAULT_RUNDIR" - - # procd stdout/err logging - config_get_bool log_stderr "$section" log_stderr 1 - procd_set_param stderr $log_stderr - config_get_bool log_stdout "$section" log_stdout 1 - procd_set_param stdout $log_stdout - - # hostnames for local DNS - config_list_foreach "$section" hostname_config add_hostname_config - - #add rpz files - config_list_foreach "$section" rpz_file add_rpz_file - - config_load dhcp - config_foreach get_local_domain dnsmasq - - if [ "$STATIC_DOMAINS" = "1" ]; then - config_foreach set_local_host host - config_foreach set_local_host domain - fi - - config_load resolver - - # load hints config - set_param_func "hints.config" "'$HINTS_CONFIG'" -} - -run_instance() { - procd_open_instance - procd_set_param file /etc/config/resolver - procd_set_param command "$PROG" - procd_set_param respawn - modify_rundir - init_header - load_uci_config_kresd - load_uci_config_common - procd_close_instance - ( sleep 60 # Wait for resolver to start working and system to boot up - if ! ip -6 r s | grep -q '^default' &&\ - ping -c 1 api.turris.cz > /dev/null 2>&1 && \ - ! ping -6 -c 1 api.turris.cz > /dev/null 2>&1; then - echo "net.ipv6 = false" | socat - UNIX-CONNECT:$(sleep 5; ls -1 $DEFAULT_RUNDIR/tty/*) > /dev/null 2>&1 - fi) & -} - - -print_dns_config() { - local name description enable_tls port ipv4 ipv6 pin_sha256 hostname ca_file - local net_ipv6 net_ipv4 - - name="$1" - description="$2" - enable_tls="$3" - port="$4" - ipv4="$5" - ipv6="$6" - pin_sha256="$7" - hostname="$8" - ca_file="$9" - net_ipv4="$10" - net_ipv6="$11" - - - if [ "$net_ipv4" == "1" ] && [ "$net_ipv6" == "1" ]; then - if [ ! -z "$ipv4" ] && [ ! -z "$ipv6" ]; then - ip_list="$ipv4 $ipv6" - elif [ ! -z "$ipv4" ] && [ -z "$ipv6" ]; then - ip_list="$ipv4" - elif [ -z "$ipv4" ] && [ ! -z "$ipv6" ]; then - ip_list="$ipv6" - else - echo "Error ipv4 and ipv6 not set !" - exit -1 - fi - elif [ "$net_ipv4" != "1" ] && [ "$net_ipv6" == "1" ]; then - if [ ! -z "$ipv6" ]; then - ip_list="$ipv6" - else - echo "Error ipv6 not set !" - exit -1 - fi - elif [ "$net_ipv4" == "1" ] && [ "$net_ipv6" != "1" ]; then - if [ ! -z "$ipv4" ]; then - ip_list="$ipv4" - else - echo "Error ipv4 not set !" - exit -1 - fi - else - echo "net_ipv4=$net_ipv4 net_ipv6=$net_ipv6" - echo "Error cannot choose IP address because of net_ipv4 and net_ipv6 settings!" - exit -1 - fi - - #([ "$enable_tls" == "0" ] || [ -z "$enable_tls" ]) && echo -ne "{" >> $CONFIGFILE - if [ "$enable_tls" == "1" ]; then - echo "policy.add(policy.all(policy.TLS_FORWARD(">>$CONFIGFILE - else - echo "policy.add(policy.all(policy.FORWARD(">>$CONFIGFILE - fi - #TODO add stub !! - - echo -ne "{" >> $CONFIGFILE - for ip in $ip_list - do - - [ "$enable_tls" == "1" ] && echo -ne "{" >> $CONFIGFILE - if [ ! -z "$ip" ]; then - #check nonstandard port - if [ "$port" == "853" ] || [ -z "$port" ]; then - echo "'$ip'" >> $CONFIGFILE - else - echo "'$ip@$port'" >> $CONFIGFILE - fi - - #check of DNS-over-TLS - if [ "$enable_tls" == "1" ]; then - - #prefer pin - if [ ! -z "$pin_sha256" ];then - echo -ne "," >> $CONFIGFILE - echo "pin_sha256='$pin_sha256'" >> $CONFIGFILE - #else use hostname and certificate from system ca storage - elif [ ! -z "$hostname" ]; then - echo -ne "," >> $CONFIGFILE - echo "hostname='$hostname'" >> $CONFIGFILE - #or use ca file if defined - if [ ! -z "$ca_file" ] && [ -f "$ca_file" ];then - echo -ne "," >> $CONFIGFILE - echo "ca_file='$ca_file'" >> $CONFIGFILE - fi - fi - fi - fi - - last_item=$(echo $ip_list | awk '{print $NF}') - if [ "$enable_tls" == "1" ] && [ "$last_item" != "$ip" ] ; then - echo -ne "}," >> $CONFIGFILE - elif [ "$enable_tls" == "1" ] && [ "$last_item" == "$ip" ] ; then - echo -ne "}" >> $CONFIGFILE - elif ([ "$enable_tls" == "0" ] || [ -z "$enable_tls" ]) && [ "$last_item" != "$ip" ]; then - echo -ne "," >> $CONFIGFILE - fi - done - echo -ne "}" >> $CONFIGFILE - echo ")))" >>$CONFIGFILE -} - -dns_config_load() { - local config_name config_file ret - local name description enable_tls port ipv4 ipv6 pin_sha256 hostname ca_file - local net_ipv4 net_ipv6 - - config_name="$1" - config_file="/etc/resolver/dns_servers/${config_name}.conf" - config_load resolver - - #check file config file - if [ -f "$config_file" ]; then - enable_tls="0" #set default value - config_get net_ipv6 "common" "net_ipv6" 1 - config_get net_ipv4 "common" "net_ipv4" 1 - - echo "loading file from ... $config_file" - source $config_file - echo print_dns_config "$name" "$description" "$enable_tls" "$port" "$ipv4" "$ipv6" "$pin_sha256" "$hostname" "$ca_file" "$net_ipv4" "$net_ipv6" - print_dns_config "$name" "$description" "$enable_tls" "$port" "$ipv4" "$ipv6" "$pin_sha256" "$hostname" "$ca_file" "$net_ipv4" "$net_ipv6" - else - echo "Error DNS Resolver config not found!" - fi - echo "ret_value_dns_load=$ret" -} - - -start_service() { - config_load resolver - run_instance -} - -reload_service() -{ - stop - start -} diff -Nru knot-resolver-5.1.1/distro/turris/files/kresd.postinst knot-resolver-5.2.1/distro/turris/files/kresd.postinst --- knot-resolver-5.1.1/distro/turris/files/kresd.postinst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/turris/files/kresd.postinst 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-2.0 -set -x - -# convert static_domain from kresd to common section -stat_dom=$(uci -q get resolver.kresd.static_domains) -if [ ! -z "$stat_dom" ]; then - uci set resolver.common.static_domains="$stat_dom" - uci delete resolver.kresd.static_domains - uci commit resolver -fi - -/etc/init.d/resolver restart diff -Nru knot-resolver-5.1.1/distro/turris/Makefile knot-resolver-5.2.1/distro/turris/Makefile --- knot-resolver-5.1.1/distro/turris/Makefile 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/distro/turris/Makefile 1970-01-01 00:00:00.000000000 +0000 @@ -1,108 +0,0 @@ -# Copyright (C) 2015-2019 CZ.NIC, z. s. p. o. (https://www.nic.cz/) -# SPDX-License-Identifier: GPL-2.0 - -PKG_RELRO_FULL:=0 - -include $(TOPDIR)/rules.mk - -PKG_NAME:=knot-resolver -PKG_VERSION:=__VERSION__ -PKG_RELEASE:=1 - -PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz - -PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION) - -PKG_MAINTAINER:=Knot DNS -PKG_LICENSE:=GPL-3.0 - -PKG_INSTALL:=1 - -include $(INCLUDE_DIR)/package.mk - -define Package/knot-resolver - SECTION:=net - CATEGORY:=Network - SUBMENU:=IP Addresses and Names - TITLE:=Knot DNS Resolver - URL:=https://gitlab.labs.nic.cz/knot/resolver - DEPENDS=+knot-libs +knot-libzscanner +libuv +luajit +dnssec-rootkey +resolver-conf +libstdcpp - PROVIDES:=dns-resolver -endef - -define Package/knot-resolver/description - The Knot DNS Resolver is a minimalistic caching resolver implementation. -endef - -MAKE_FLAGS += \ - PREFIX=/usr \ - ETCDIR=/etc/kresd - -define Build/Patch - $(Build/Patch/Default) - cd $(PKG_BUILD_DIR) && \ - sed -i -e 's/\(.*find_lib,hiredis.*\)/HAS_hiredis := no/' \ - -e 's/\(.*find_lib,libmemcached.*\)/HAS_libmemcached := no/' Makefile -endef - -define Package/knot-resolver/install - $(INSTALL_DIR) $(1)/usr/bin - $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/*bin/kresd $(1)/usr/bin/ - - $(INSTALL_DIR) $(1)/usr/lib - $(CP) $(PKG_INSTALL_DIR)/usr/lib/libkres.so* $(1)/usr/lib - $(CP) $(PKG_INSTALL_DIR)/usr/lib/kdns_modules $(1)/usr/lib - - $(INSTALL_DIR) $(1)/etc/init.d - $(INSTALL_BIN) ./files/kresd.init $(1)/etc/init.d/kresd - - $(INSTALL_DIR) $(1)/etc/kresd - $(CP) $(PKG_BUILD_DIR)/etc/icann-ca.pem $(1)/etc/kresd - $(CP) $(PKG_BUILD_DIR)/etc/root.hints $(1)/etc/kresd - $(INSTALL_BIN) ./files/kresd.postinst $(1)/etc/kresd/kresd.postinst.sh - $(INSTALL_BIN) ./files/convert_config.sh $(1)/etc/kresd/convert_config.sh -endef - -define Package/$(PKG_NAME)/postinst -#!/bin/sh -set -x -if [ -z "$$IPKG_INSTROOT" ]; then -/etc/init.d/dnsmasq restart -sleep 2 # Cooldown for above (problems in times of reinstall) -/etc/kresd/kresd.postinst.sh && rm /etc/kresd/kresd.postinst.sh -/etc/kresd/convert_config.sh && rm /etc/kresd/convert_config.sh -if [ "`uci -q get resolver.common.prefered_resolver`" = kresd ] && uci -q get resolver.common.forward_upstream | egrep -q '(1|yes|true|enabled|on)'; then - ping -c 1 api.turris.cz 2> /dev/null >&2 || { - uci set resolver.common.forward_upstream=0 - uci commit - /etc/init.d/resolver restart - if ping -c 1 api.turris.cz 2> /dev/null >&2; then - create_notification -s error "DNS servery vašeho poskytovatele internetu nefungují úplně dobře - pravděpodobně nepodporují DNSSEC. Bylo proto vypnuto forwardování a váš router bude nyní vyhodnocovat DNS dotazy sám." "Your ISPs DNS servers does not work properly - most likely they don't support DNSSEC. Therefore DNS forwarding was turned off and your router will now resolve all DNS queries by itself." - else - uci set resolver.common.forward_upstream=1 - uci commit - /etc/init.d/resolver restart - fi - } -fi -fi -endef - -define Package/$(PKG_NAME)/prerm -#!/bin/sh -set -ex -if [ -z "$$IPKG_INSTROOT" ]; then -# Remove the script if it is there. -if [ -e /etc/init.d/kresd ] ; then /etc/init.d/kresd stop ; /etc/init.d/kresd disable ; fi -fi -endef - -define Package/knot-resolver/postrm -#!/bin/sh -if [ -z "$$IPKG_INSTROOT" ]; then -[ \! -x /etc/init.d/resolver ] || /etc/init.d/resolver restart -fi -endef - -$(eval $(call BuildPackage,knot-resolver)) - diff -Nru knot-resolver-5.1.1/doc/build.rst knot-resolver-5.2.1/doc/build.rst --- knot-resolver-5.1.1/doc/build.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/build.rst 2020-12-09 09:44:29.000000000 +0000 @@ -12,11 +12,11 @@ Beware that some 64-bit systems with LuaJIT 2.1 may be affected by `a problem `_ -- Linux on x86_64 is unaffected but `Linux on aarch64 is -`_. +`_. .. code-block:: bash - $ git clone --recursive https://gitlab.labs.nic.cz/knot/knot-resolver.git + $ git clone --recursive https://gitlab.nic.cz/knot/knot-resolver.git Dependencies ------------ @@ -50,7 +50,9 @@ "cmocka_", "``unit tests``", "Unit testing framework." "Doxygen_", "``documentation``", "Generating API documentation." "Sphinx_ and sphinx_rtd_theme_", "``documentation``", "Building this - HTML/PDF documentation." + documentation." + "Texinfo_", "``documentation``", "Generating this documentation in Info + format." "breathe_", "``documentation``", "Exposing Doxygen API doc to Sphinx." "libsystemd_", "``daemon``", "Systemd watchdog support." "libprotobuf_ 3.0+", "``modules/dnstap``", "Protocol Buffers support for @@ -81,9 +83,9 @@ here's an overview for several platforms. * **Debian/Ubuntu** - Current stable doesn't have new enough Meson - and libknot. Use repository above or build them yourself. Fresh list of dependencies can be found in `Debian control file in our repo `_, search for "Build-Depends". + and libknot. Use repository above or build them yourself. Fresh list of dependencies can be found in `Debian control file in our repo `_, search for "Build-Depends". -* **CentOS/Fedora/RHEL/openSUSE** - Fresh list of dependencies can be found in `RPM spec file in our repo `_, search for "BuildRequires". +* **CentOS/Fedora/RHEL/openSUSE** - Fresh list of dependencies can be found in `RPM spec file in our repo `_, search for "BuildRequires". * **FreeBSD** - when installing from ports, all dependencies will install automatically, corresponding to the selected options. @@ -191,8 +193,8 @@ .. _build-html-doc: -HTML Documentation ------------------- +Documentation +------------- To check for documentation dependencies and allow its installation, use ``-Ddoc=enabled``. The documentation doesn't build automatically. Instead, @@ -232,7 +234,7 @@ prefix, other directories can be set in a similar fashion, see ``meson setup --help`` * ``-Dsystemd_files=enabled`` for systemd unit files -* ``-Ddoc=enabled`` for offline html documentation (see :ref:`build-html-doc`) +* ``-Ddoc=enabled`` for offline documentation (see :ref:`build-html-doc`) * ``-Dinstall_kresd_conf=enabled`` to install default config file * ``-Dclient=enabled`` to force build of kresc * ``-Dunit_tests=enabled`` to force build of unit tests @@ -288,13 +290,14 @@ .. _breathe: https://github.com/michaeljones/breathe .. _Sphinx: http://sphinx-doc.org/ .. _sphinx_rtd_theme: https://pypi.python.org/pypi/sphinx_rtd_theme +.. _Texinfo: https://www.gnu.org/software/texinfo/ .. _pkg-config: https://www.freedesktop.org/wiki/Software/pkg-config/ -.. _libknot: https://gitlab.labs.nic.cz/knot/knot-dns +.. _libknot: https://gitlab.nic.cz/knot/knot-dns .. _cmocka: https://cmocka.org/ .. _lua-http: https://luarocks.org/modules/daurnimator/http .. _lua-cqueues: https://25thandclement.com/~william/projects/cqueues.html .. _boot2docker: http://boot2docker.io/ -.. _deckard: https://gitlab.labs.nic.cz/knot/deckard +.. _deckard: https://gitlab.nic.cz/knot/deckard .. _libsystemd: https://www.freedesktop.org/wiki/Software/systemd/ .. _dnstap: http://dnstap.info/ .. _libprotobuf: https://developers.google.com/protocol-buffers/ diff -Nru knot-resolver-5.1.1/doc/config-logging-monitoring.rst knot-resolver-5.2.1/doc/config-logging-monitoring.rst --- knot-resolver-5.1.1/doc/config-logging-monitoring.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/config-logging-monitoring.rst 2020-12-09 09:44:29.000000000 +0000 @@ -38,6 +38,7 @@ modules-bogus_log modules-stats + daemon-bindings-worker modules-nsid modules-http-trace modules-watchdog diff -Nru knot-resolver-5.1.1/doc/config-network.rst knot-resolver-5.2.1/doc/config-network.rst --- knot-resolver-5.1.1/doc/config-network.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/config-network.rst 2020-12-09 09:44:29.000000000 +0000 @@ -43,7 +43,6 @@ daemon-bindings-net_server daemon-bindings-net_tlssrv modules-http - modules-http-doh Client (retrieving answers from servers) ======================================== diff -Nru knot-resolver-5.1.1/doc/config-no-systemd-privileges.rst knot-resolver-5.2.1/doc/config-no-systemd-privileges.rst --- knot-resolver-5.1.1/doc/config-no-systemd-privileges.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/config-no-systemd-privileges.rst 2020-12-09 09:44:29.000000000 +0000 @@ -14,7 +14,8 @@ * ``CAP_NET_BIND_SERVICE`` is required to bind to well-known ports. * ``CAP_SETPCAP`` when this capability is available, kresd drops any extra - privileges after the daemon successfully starts. + capabilities after the daemon successfully starts when running as + a non-root user. Running as non-privileged user ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -62,7 +63,3 @@ proccesses have unconstrained access to the complete system at runtime. While not recommended, it is also possible to run kresd directly as root. - -Please note the process will still attempt to drop capabilities after startup. -Among other things, this means the cache directory should belong to root to -have write access. diff -Nru knot-resolver-5.1.1/doc/config-no-systemd.rst knot-resolver-5.2.1/doc/config-no-systemd.rst --- knot-resolver-5.1.1/doc/config-no-systemd.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/config-no-systemd.rst 2020-12-09 09:44:29.000000000 +0000 @@ -34,4 +34,4 @@ config-no-systemd-processes config-no-systemd-privileges -.. _`#529`: https://gitlab.labs.nic.cz/knot/knot-resolver/issues/529 +.. _`#529`: https://gitlab.nic.cz/knot/knot-resolver/issues/529 diff -Nru knot-resolver-5.1.1/doc/config-performance.rst knot-resolver-5.2.1/doc/config-performance.rst --- knot-resolver-5.1.1/doc/config-performance.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/config-performance.rst 2020-12-09 09:44:29.000000000 +0000 @@ -32,4 +32,5 @@ modules-rfc7706 modules-priming modules-edns_keepalive + daemon-bindings-net_xdpsrv diff -Nru knot-resolver-5.1.1/doc/conf.py knot-resolver-5.2.1/doc/conf.py --- knot-resolver-5.1.1/doc/conf.py 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/conf.py 2020-12-09 09:44:29.000000000 +0000 @@ -83,3 +83,13 @@ ('index', 'libkres', u'libkres documentation', [u'CZ.NIC Labs'], 1) ] + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'knot-resolver', u'Knot Resolver', u'CZ.NIC Labs', + 'Knot Resolver', 'Caching DNS resolver.', 'Network services'), +] diff -Nru knot-resolver-5.1.1/doc/daemon-bindings-cache.rst knot-resolver-5.2.1/doc/daemon-bindings-cache.rst --- knot-resolver-5.1.1/doc/daemon-bindings-cache.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/daemon-bindings-cache.rst 2020-12-09 09:44:29.000000000 +0000 @@ -186,7 +186,7 @@ .. function:: cache.stats() - Return table with low-level statistics for each internal cache operation. + Return table with low-level statistics for internal cache operation and storage. This counts each access to cache and does not directly map to individual DNS queries or resource records. For query-level statistics see :ref:`stats module `. @@ -196,23 +196,27 @@ .. code-block:: lua > cache.stats() - [read_leq_miss] => 4 - [write] => 189 - [read_leq] => 9 - [read] => 4313 - [read_miss] => 1143 - [open] => 0 + [clear] => 0 [close] => 0 - [remove_miss] => 0 [commit] => 117 - [match_miss] => 2 - [match] => 21 [count] => 2 - [clear] => 0 + [count_entries] => 6187 + [match] => 21 + [match_miss] => 2 + [open] => 0 + [read] => 4313 + [read_leq] => 9 + [read_leq_miss] => 4 + [read_miss] => 1143 [remove] => 17 + [remove_miss] => 0 + [usage_percent] => 15.625 + [write] => 189 + Cache operation `read_leq` (*read less or equal*, i.e. range search) was requested 9 times, and 4 out of 9 operations were finished with *cache miss*. + Cache contains 6187 internal entries which occupy 15.625 % cache size. .. function:: cache.max_ttl([ttl]) diff -Nru knot-resolver-5.1.1/doc/daemon-bindings-net_dns_tweaks.rst knot-resolver-5.2.1/doc/daemon-bindings-net_dns_tweaks.rst --- knot-resolver-5.1.1/doc/daemon-bindings-net_dns_tweaks.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/daemon-bindings-net_dns_tweaks.rst 2020-12-09 09:44:29.000000000 +0000 @@ -6,19 +6,30 @@ Following settings change low-level details of DNS protocol implementation. Default values should not be changed except for very special cases. -.. function:: net.bufsize([udp_bufsize]) +.. function:: net.bufsize([udp_downstream_bufsize][, udp_upstream_bufsize]) - Get/set maximum EDNS payload size advertised in DNS packets. Default is 4096 bytes and the default will be lowered to value around 1220 bytes in future, once `DNS Flag Day 2020 `_ becomes effective. + Get/set maximum EDNS payload size advertised in DNS packets. Different values can be configured for communication downstream (towards clients) and upstream (towards other DNS servers). Set and also get operations use values in this order. - Minimal value allowed by standard :rfc:`6891` is 512 bytes, which is equal to DNS packet size without Extension Mechanisms for DNS. Value 1220 bytes is minimum size required in DNSSEC standard :rfc:`4035`. + Default is 1232 bytes which was chosed to minimize risk of `issues caused by IP fragmentation `_. Further details can be found at `DNS Flag Day 2020 `_ web site. + + Minimal value allowed by standard :rfc:`6891` is 512 bytes, which is equal to DNS packet size without Extension Mechanisms for DNS. Value 1220 bytes is minimum size required by DNSSEC standard :rfc:`4035`. Example output: .. code-block:: lua + -- set downstream and upstream bufsize to value 4096 > net.bufsize(4096) - nil + -- get configured downstream and upstream bufsizes, respectively + > net.bufsize() + 4096 -- result # 1 + 4096 -- result # 2 + + -- set downstream bufsize to 4096 and upstream bufsize to 1232 + > net.bufsize(4096, 1232) + -- get configured downstream and upstream bufsizes, respectively > net.bufsize() - 4096 + 4096 -- result # 1 + 1232 -- result # 2 .. include:: ../modules/workarounds/README.rst diff -Nru knot-resolver-5.1.1/doc/daemon-bindings-net_server.rst knot-resolver-5.2.1/doc/daemon-bindings-net_server.rst --- knot-resolver-5.1.1/doc/daemon-bindings-net_server.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/daemon-bindings-net_server.rst 2020-12-09 09:44:29.000000000 +0000 @@ -13,10 +13,12 @@ :header: "Protocol/service", "net.listen *kind*" "DNS (unencrypted UDP+TCP, :rfc:`1034`)","``dns``" - ":ref:`DNS-over-TLS (DoT) `","``tls``" - ":ref:`mod-http-doh`","``doh``" + "DNS (unencrypted UDP, :ref:`using XDP Linux API `)","``xdp``" + ":ref:`dns-over-tls`","``tls``" + ":ref:`dns-over-https`","``doh2``" ":ref:`Web management `","``webmgmt``" ":ref:`Control socket `","``control``" + ":ref:`mod-http-doh`","``doh``" .. note:: By default, **unencrypted DNS and DNS-over-TLS** are configured to **listen on localhost**. @@ -37,8 +39,9 @@ :header: "**Network protocol**", "**Configuration command**" "DNS (UDP+TCP, :rfc:`1034`)","``net.listen('192.0.2.123', 53)``" - ":ref:`DNS-over-TLS (DoT) `","``net.listen('192.0.2.123', 853, { kind = 'tls' })``" - ":ref:`mod-http-doh`","``net.listen('192.0.2.123', 443, { kind = 'doh' })``" + "DNS (UDP, :ref:`using XDP `)","``net.listen('192.0.2.123', 53, { kind = 'xdp' })``" + ":ref:`dns-over-tls`","``net.listen('192.0.2.123', 853, { kind = 'tls' })``" + ":ref:`dns-over-https`","``net.listen('192.0.2.123', 443, { kind = 'doh2' })``" ":ref:`Web management `","``net.listen('192.0.2.123', 8453, { kind = 'webmgmt' })``" ":ref:`Control socket `","``net.listen('/tmp/kres.control', nil, { kind = 'control' })``" @@ -52,12 +55,11 @@ net.listen(net.eth0, 853, { kind = 'tls' }) net.listen('192.0.2.1', 53, { freebind = true }) net.listen({'127.0.0.1', '::1'}, 53, { kind = 'dns' }) - net.listen('::', 443, { kind = 'doh' }) -- see http module + net.listen('::', 443, { kind = 'doh2' }) net.listen('::', 8453, { kind = 'webmgmt' }) -- see http module net.listen('/tmp/kresd-socket', nil, { kind = 'webmgmt' }) -- http module supports AF_UNIX - -.. warning:: Make sure you read section :ref:`mod-http-doh` before exposing - the DNS-over-HTTP protocol to outside. + net.listen('eth0', 53, { kind = 'xdp' }) + net.listen('192.0.2.123', 53, { kind = 'xdp', nic_queue = 0 }) .. warning:: On machines with multiple IP addresses avoid listening on wildcards ``0.0.0.0`` or ``::``. Knot Resolver could answer from different IP @@ -111,6 +113,16 @@ [protocol] => tcp } } + [4] => { + [kind] => xdp + [transport] => { + [family] => inet4+inet6 + [interface] => eth2 + [nic_queue] => 0 + [port] => 53 + [protocol] => udp + } + } .. function:: net.interfaces() @@ -151,3 +163,4 @@ .. _`dnsproxy module`: https://www.knot-dns.cz/docs/2.7/html/modules.html#dnsproxy-tiny-dns-proxy + diff -Nru knot-resolver-5.1.1/doc/daemon-bindings-net_tlssrv.rst knot-resolver-5.2.1/doc/daemon-bindings-net_tlssrv.rst --- knot-resolver-5.1.1/doc/daemon-bindings-net_tlssrv.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/daemon-bindings-net_tlssrv.rst 2020-12-09 09:44:29.000000000 +0000 @@ -2,9 +2,8 @@ .. _tls-server-config: -DNS-over-TLS server (DoT) -------------------------- -DoT encrypts DNS traffic with Transport Security Layer protocol and thus protects DNS traffic from certain types of attacks. +DoT and DoH (encrypted DNS) +--------------------------- .. warning:: @@ -14,17 +13,77 @@ See `slides `_ or `the article itself `_. -DNS-over-TLS server (:rfc:`7858`) is enabled by default on localhost. -Information how to configure listening on specific IP addresses is in previous sections: +DoT and DoH encrypt DNS traffic with Transport Layer Security (TLS) protocol +and thus protects DNS traffic from certain types of attacks. + +You can learn more about DoT and DoH and their implementation in Knot Resolver +in `this article +`_. + +.. _dns-over-tls: + +DNS-over-TLS (DoT) +^^^^^^^^^^^^^^^^^^ + +DNS-over-TLS server (:rfc:`7858`) can be configured using ``tls`` kind in +:func:`net.listen()`. It is enabled on localhost by default. + +For certificate configuration, refer to :ref:`dot-doh-config-options`. + +.. _dns-over-https: + +DNS-over-HTTPS (DoH) +^^^^^^^^^^^^^^^^^^^^ + +.. note:: Knot Resolver currently offers two DoH implementations. It is + recommended to use this new implementation, which is more reliable, scalable + and has fewer dependencies. Make sure to use ``doh2`` kind in + :func:`net.listen()` to select this implementation. + +.. tip:: Independent information about political controversies around the + DoH deployment by default can be found in blog posts `DNS Privacy at IETF + 104 `_ and `More DOH + `_ by Geoff Huston and + `Centralised DoH is bad for Privacy, in 2019 and beyond + `_ + by Bert Hubert. + +DNS-over-HTTPS server (:rfc:`8484`) can be configured using ``doh2`` kind in :func:`net.listen()`. -By default a self-signed certificate is generated. For serious deployments +This implementation supports HTTP/2 (:rfc:`7540`). Queries can be sent to the +``/dns-query`` endpoint, e.g.: + +.. code-block:: bash + + $ kdig @127.0.0.1 +https www.knot-resolver.cz AAAA + +**Only TLS version 1.3 (or higher) is supported with DNS-over-HTTPS.** The +additional considerations for TLS 1.2 required by HTTP/2 are not implemented +(:rfc:`7540#section-9.2`). + +.. warning:: Take care when configuring your server to listen on well known + HTTPS port. If an unrelated HTTPS service is running on the same port with + REUSEPORT enabled, you will end up with both services malfunctioning. + +.. _dot-doh-config-options: + +Configuration options +^^^^^^^^^^^^^^^^^^^^^ + +.. note:: These settings affect both DNS-over-TLS and DNS-over-HTTPS (except + the legacy implementation). + +A self-signed certificate is generated by default. For serious deployments it is strongly recommended to configure your own TLS certificates signed by a trusted CA. This is done using function :c:func:`net.tls()`. .. function:: net.tls([cert_path], [key_path]) - Get/set path to a server TLS certificate and private key for DNS/TLS. + When called with path arguments, the function loads the server TLS + certificate and private key for DoT and DoH. + + When called without arguments, the command returns the currently configured paths. Example output: @@ -34,15 +93,10 @@ > net.tls() -- print configured paths ("/etc/knot-resolver/server-cert.pem", "/etc/knot-resolver/server-key.pem") -.. function:: net.tls_padding([true | false]) - - Get/set EDNS(0) padding of answers to queries that arrive over TLS - transport. If set to `true` (the default), it will use a sensible - default padding scheme, as implemented by libknot if available at - compile time. If set to a numeric value >= 2 it will pad the - answers to nearest *padding* boundary, e.g. if set to `64`, the - answer will have size of a multiple of 64 (64, 128, 192, ...). If - set to `false` (or a number < 2), it will disable padding entirely. + .. tip:: The certificate files aren't automatically reloaded on change. If + you update the certificate files, e.g. using ACME, you have to either + restart the service(s) or call this function again using + :ref:`control-sockets`. .. function:: net.tls_sticket_secret([string with pre-shared secret]) @@ -72,3 +126,14 @@ The same as :func:`net.tls_sticket_secret`, except the secret is read from a (binary) file. + +.. function:: net.tls_padding([true | false]) + + Get/set EDNS(0) padding of answers to queries that arrive over TLS + transport. If set to `true` (the default), it will use a sensible + default padding scheme, as implemented by libknot if available at + compile time. If set to a numeric value >= 2 it will pad the + answers to nearest *padding* boundary, e.g. if set to `64`, the + answer will have size of a multiple of 64 (64, 128, 192, ...). If + set to `false` (or a number < 2), it will disable padding entirely. + diff -Nru knot-resolver-5.1.1/doc/daemon-bindings-net_xdpsrv.rst knot-resolver-5.2.1/doc/daemon-bindings-net_xdpsrv.rst --- knot-resolver-5.1.1/doc/daemon-bindings-net_xdpsrv.rst 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/daemon-bindings-net_xdpsrv.rst 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,138 @@ +.. SPDX-License-Identifier: GPL-3.0-or-later + +.. _dns-over-xdp: + +XDP for higher UDP performance +------------------------------ + +.. warning:: + As of version 5.2.0, XDP support in Knot Resolver is considered + experimental. The impact on overall throughput and performance may not + always be beneficial. + +Using XDP allows significant speedup of UDP packet processing in recent Linux kernels, +especially with some network drivers that implement good support. +The basic idea is that for selected packets the Linux networking stack is bypassed, +and some drivers can even directly use the user-space buffers for reading and writing. + +.. TODO perhaps some hint/link about how significant speedup one might get? (link to some talk video?) + +Prerequisites +^^^^^^^^^^^^^ +.. this is mostly copied from knot-dns doc/operations.rst + +.. warning:: + Bypassing the network stack has significant implications, such as bypassing the firewall + and monitoring solutions. + Make sure you're familiar with the trade-offs before using this feature. + Read more in :ref:`dns-over-xdp_limitations`. + +* Linux kernel 4.18+ (5.x+ is recommended for optimal performance) compiled with + the `CONFIG_XDP_SOCKETS=y` option. XDP isn't supported in other operating systems. +* libknot compiled with XDP support +* **A multiqueue network card with native XDP support is highly recommended**, + otherwise the performance gain will be much lower and you may encounter + issues due to XDP emulation. + Successfully tested cards: + + * Intel series 700 (driver `i40e`), maximum number of queues per interface is 64. + * Intel series 500 (driver `ixgbe`), maximum number of queues per interface is 64. + The number of CPUs available has to be at most 64! + + +Set up +^^^^^^ +.. first parts are mostly copied from knot-dns doc/operations.rst + +The server instances need additional Linux **capabilities** during startup. +(Or you could start them as `root`.) +Execute command + +.. code-block:: bash + + systemctl edit kresd@.service + +And insert these lines: + +.. code-block:: ini + + [Service] + CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_ADMIN CAP_SYS_RESOURCE + AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_ADMIN CAP_SYS_RESOURCE + +.. TODO suggest some way for ethtool -L? Perhaps via systemd units? + +You want the same number of kresd instances and network **queues** on your card; +you can use ``ethtool -L`` before the services start. +With XDP this is more important than with vanilla UDP, as we only support one instance +per queue and unclaimed queues will fall back to vanilla UDP. +Ideally you can set these numbers as high as the number of CPUs that you want kresd to use. + +Modification of ``/etc/knot-resolver/kresd.conf`` may often be quite simple, for example: + +.. code-block:: lua + + net.listen('eth2', 53, { kind = 'xdp' }) + net.listen('203.0.113.53', 53, { kind = 'dns' }) + +Note that you want to also keep the vanilla DNS line to service TCP +and possibly any fallback UDP (e.g. from unclaimed queues). +XDP listening is in principle done on queues of whole network interfaces +and the target addresses of incoming packets aren't checked in any way, +but you are still allowed to specify interface by an address +(if it's unambiguous at that moment): + +.. code-block:: lua + + net.listen('203.0.113.53', 53, { kind = 'xdp' }) + net.listen('203.0.113.53', 53, { kind = 'dns' }) + +The default selection of queues is tailored for the usual naming convention: +``kresd@1.service``, ``kresd@2.service``, ... +but you can still specify them explicitly, e.g. the default is effectively the same as: + +.. code-block:: lua + + net.listen('eth2', 53, { kind = 'xdp', nic_queue = env.SYSTEMD_INSTANCE - 1 }) + + +Optimizations +^^^^^^^^^^^^^ +.. this is basically copied from knot-dns doc/operations.rst + +Some helpful commands: + +.. code-block:: text + + ethtool -N rx-flow-hash udp4 sdfn + ethtool -N rx-flow-hash udp6 sdfn + ethtool -L combined + ethtool -G rx tx + renice -n 19 -p $(pgrep '^ksoftirqd/[0-9]*$') + +.. TODO CPU affinities? `CPUAffinity=%i` in systemd unit sounds good. + + +.. _dns-over-xdp_limitations: + +Limitations +^^^^^^^^^^^ +.. this is basically copied from knot-dns doc/operations.rst + +* VLAN segmentation is not supported. +* MTU higher than 1792 bytes is not supported. +* Multiple BPF filters per one network device are not supported. +* Symmetrical routing is required (query source MAC/IP addresses and + reply destination MAC/IP addresses are the same). +* Systems with big-endian byte ordering require special recompilation of libknot. +* IPv4 header and UDP checksums are not verified on received DNS messages. +* DNS over XDP traffic is not visible to common system tools (e.g. firewall, tcpdump etc.). +* BPF filter is not automatically unloaded from the network device. Manual filter unload:: + + ip link set dev xdp off + +* Knot Resolver only supports using XDP towards clients currently (not towards upstreams). +* When starting up an XDP socket you may get a harmless warning:: + + libbpf: Kernel error message: XDP program already attached + diff -Nru knot-resolver-5.1.1/doc/daemon-bindings-worker.rst knot-resolver-5.2.1/doc/daemon-bindings-worker.rst --- knot-resolver-5.1.1/doc/daemon-bindings-worker.rst 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/daemon-bindings-worker.rst 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,35 @@ +.. SPDX-License-Identifier: GPL-3.0-or-later + +Scripting worker +^^^^^^^^^^^^^^^^ + +Worker is a service over event loop that tracks and schedules outstanding queries, +you can see the statistics or schedule new queries. It also contains information about +specified worker count and process rank. + +.. envvar:: worker.id + + Value from environment variable ``SYSTEMD_INSTANCE``, + or if it is not set, :envvar:`PID ` (string). + +.. envvar:: worker.pid + + Current worker process PID (number). + +.. function:: worker.stats() + + Return table of statistics. See member descriptions in :c:type:`worker_stats`. + A few fields are added, mainly from POSIX ``getrusage()``: + + * ``usertime`` and ``systime`` -- CPU time used, in seconds + * ``pagefaults`` -- the number of hard page faults, i.e. those that required I/O activity + * ``swaps`` -- the number of times the process was “swapped” out of main memory; unused on Linux + * ``csw`` -- the number of context switches, both voluntary and involuntary + * ``rss`` -- current memory usage in bytes, including whole cache (resident set size) + + Example: + + .. code-block:: lua + + print(worker.stats().concurrent) + diff -Nru knot-resolver-5.1.1/doc/daemon-scripting.rst knot-resolver-5.2.1/doc/daemon-scripting.rst --- knot-resolver-5.1.1/doc/daemon-scripting.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/daemon-scripting.rst 2020-12-09 09:44:29.000000000 +0000 @@ -22,8 +22,8 @@ --------------- Control socket acts like "an interactive configuration file" so all actions available in configuration file can be executed interactively using the control -socket. One possible use-case is reconfiguring Resolver instances from another -program, e.g. a maintenance script. +socket. One possible use-case is reconfiguring the resolver instances from +another program, e.g. a maintenance script. .. note:: Each instance of Knot Resolver exposes its own control socket. Take that into account when scripting deployments with @@ -32,12 +32,10 @@ When Knot Resolver is started using Systemd (see section :ref:`quickstart-startup`) it creates a control socket in path ``/run/knot-resolver/control/$ID``. Connection to the socket can -be made from command line using e.g. ``netcat`` or ``socat``: +be made from command line using e.g. ``socat``: .. code-block:: bash - $ nc -U /run/knot-resolver/control/1 - or $ socat - UNIX-CONNECT:/run/knot-resolver/control/1 When successfully connected to a socket, the command line should change to @@ -61,6 +59,78 @@ list of sockets corresponds to the list of processes, and you can test the process for liveliness by connecting to the UNIX socket. +.. function:: map(lua_snippet) + + Executes the provided string as lua code on every running resolver instance + and returns the results as a table. + + Key ``n`` is always present in the returned table and specifies the total + number of instances the command was executed on. The table also contains + results from each instance accessible through keys ``1`` to ``n`` + (inclusive). If any instance returns ``nil``, it is not explicitly part of + the table, but you can detect it by iterating through ``1`` to ``n``. + + .. code-block:: lua + + > map('worker.id') -- return an ID of every active instance + { + '2', + '1', + ['n'] = 2, + } + > map('worker.id == "1" or nil') -- example of `nil` return value + { + [2] = true, + ['n'] = 2, + } + + The order of instances isn't guaranteed or stable. When you need to identify + the instances, you may use ``kluautil.kr_table_pack()`` function to return multiple + values as a table. It uses similar semantics with ``n`` as described above + to allow ``nil`` values. + + .. code-block:: lua + + > map('require("kluautil").kr_table_pack(worker.id, stats.get("answer.total"))') + { + { + '2', + 42, + ['n'] = 2, + }, + { + '1', + 69, + ['n'] = 2, + }, + ['n'] = 2, + } + + If the command fails on any instance, an error is returned and the execution + is in an undefined state (the command might not have been executed on all + instances). When using the ``map()`` function to execute any code that might + fail, your code should be wrapped in `pcall() + `_ to avoid this + issue. + + .. code-block:: lua + + > map('require("kluautil").kr_table_pack(pcall(net.tls, "cert.pem", "key.pem"))') + { + { + true, -- function suceeded + true, -- function return value(s) + ['n'] = 2, + }, + { + false, -- function failed + 'error occurred...', -- the returned error message + ['n'] = 2, + }, + ['n'] = 2, + } + + Lua scripts ----------- diff -Nru knot-resolver-5.1.1/doc/meson.build knot-resolver-5.2.1/doc/meson.build --- knot-resolver-5.1.1/doc/meson.build 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/meson.build 2020-12-09 09:44:29.000000000 +0000 @@ -19,7 +19,7 @@ install_man(man_kresd) -# html documentation +# html and info documentation if get_option('doc') == 'enabled' message('--- doc dependencies ---') doxygen = find_program('doxygen') @@ -27,6 +27,7 @@ if not sphinx_build.found() sphinx_build = find_program('sphinx-build') endif + makeinfo = find_program('makeinfo', required: false) # python dependencies: breathe, sphinx_rtd_theme python_breathe = run_command('python3', '-c', 'import breathe') @@ -51,9 +52,18 @@ # install html docs install_subdir( - join_paths(meson.current_source_dir(), 'html'), + meson.current_source_dir() / 'html', install_dir: doc_dir, ) + + if makeinfo.found() + # install info docs + install_subdir( + meson.current_source_dir() / 'texinfo' / '.install', + strip_directory: true, + install_dir: info_dir, + ) + endif endif make_doc = find_program('../scripts/make-doc.sh') @@ -61,3 +71,9 @@ 'doc', command: make_doc, ) + + +run_target( + 'doc-strict', + command: [make_doc, '-W'], +) diff -Nru knot-resolver-5.1.1/doc/modules-http-doh.rst knot-resolver-5.2.1/doc/modules-http-doh.rst --- knot-resolver-5.1.1/doc/modules-http-doh.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/modules-http-doh.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,120 +0,0 @@ -.. SPDX-License-Identifier: GPL-3.0-or-later - -.. _mod-http-doh: - -DNS-over-HTTP (DoH) -=================== - -.. warning:: - - * DoH support was added in version 4.0.0 and is subject to change. - * DoH implementation in Knot Resolver is intended for experimentation - only as there is insufficient experience with the module - and the DoH protocol in general. - * For the time being it is recommended to run DoH endpoint - on a separate machine which is not handling normal DNS operations. - * Read about perceived benefits and risks at - `Mozilla's DoH page `_. - * It is important to understand **limits of encrypting only DNS traffic**. - Relevant security analysis can be found in article - *Simran Patil and Nikita Borisov. 2019. What can you learn from an IP?* - See `slides `_ - or `the article itself `_. - * Independent information about political controversies around the DoH - deployment by default can be found in blog posts - `DNS Privacy at IETF 104 `_ - and - `More DOH `_ - by Geoff Huston - and `Centralised DoH is bad for Privacy, in 2019 and beyond `_ - by Bert Hubert. - -Following section compares several options for running a DoH capable server. -Make sure you read through this chapter before exposing the DoH service to users. - -DoH support in Knot Resolver ----------------------------- - -The :ref:`HTTP module ` in Knot Resolver also provides support for -binary DNS-over-HTTP protocol standardized in :rfc:`8484`. - -This integrated DoH server has following properties: - -:Scenario: - HTTP module in Knot Resolver configured to provide ``/doh`` endpoint - (as shown below). - -:Advantages: - - Integrated solution provides management and monitoring in one place. - - Supports ACLs for DNS traffic based on client's IP address. - -:Disadvantages: - - Exposes Knot Resolver instance to attacks over HTTP. - - Does not offer fine grained authorization and logging at HTTP level. - - Let's Encrypt integration is not automated. - - -:ref:`Example configuration ` is part of examples for generic -HTTP module. After configuring your endpoint you can reach the DoH endpoint using -URL ``https://your.resolver.hostname.example/doh``, done! - -.. code-block:: bash - - # query for www.knot-resolver.cz AAAA - $ curl -k https://your.resolver.hostname.example/doh?dns=l1sBAAABAAAAAAAAA3d3dw1rbm90LXJlc29sdmVyAmN6AAAcAAE - -Please see section :ref:`mod-http-tls` for further details about TLS configuration. - -Alternative configurations use HTTP proxies between clients and a Knot Resolver instance: - -Normal HTTP proxy ------------------ -:Scenario: - A standard HTTP-compliant proxy is configured to proxy `GET` - and `POST` requests to HTTP endpoint `/doh` to a machine - running Knot Resolver. - -:Advantages: - - Protects Knot Resolver instance from - `some` types of attacks at HTTP level. - - Allows fine-grained filtering and logging at HTTP level. - - Let's Encrypt integration is readily available. - - Is based on mature software. - -:Disadvantages: - - Fine-grained ACLs for DNS traffic are not available because - proxy hides IP address of client sending DNS query. - - More complicated setup with two components (proxy + Knot Resolver). - -HTTP proxy with DoH support ---------------------------- -:Scenario: - HTTP proxy extended with a - `special module for DNS-over-HTTP `_. - The module transforms HTTP requests to standard DNS queries - which are then processed by Knot Resolver. - DNS replies from Knot Resolver are then transformed back to HTTP - encoding by the proxy. - -:Advantages: - - Protects Knot Resolver instance from `all` attacks at HTTP level. - - Allows fine-grained filtering and logging at HTTP level. - - Let's Encrypt integration is readily available - if proxy is based on a standard HTTP software. - -:Disadvantages: - - Fine-grained ACLs for DNS traffic are not available because - proxy hides IP address of client sending DNS query. - (Unless proxy and resolver are using non-standard packet extensions like - `DNS X-Proxied-For `_.) - - More complicated setup with three components (proxy + special module + Knot Resolver). - -Client configuration --------------------- -Most common client today is web browser Firefox, which requires manual configuration -to use your own DNS resolver. Configuration options in Firefox are described at -`Mozilla support site `_. - -.. warning:: - - Make sure you read :ref:`warnings at beginning of this section `. diff -Nru knot-resolver-5.1.1/doc/modules-http.rst knot-resolver-5.2.1/doc/modules-http.rst --- knot-resolver-5.1.1/doc/modules-http.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/modules-http.rst 2020-12-09 09:44:29.000000000 +0000 @@ -2,8 +2,8 @@ .. _mod-http: -HTTP services -============= +Other HTTP services +=================== .. tip:: In most distributions, the ``http`` module is available from a separate package ``knot-resolver-module-http``. The module isn't packaged @@ -14,18 +14,18 @@ modules to export restful APIs and websocket streams. One example is statistics module that can stream live metrics on the website, -or publish metrics on request for Prometheus scraper, and also :ref:`mod-http-doh`. +or publish metrics on request for Prometheus scraper. By default this module provides two kinds of endpoints, and unlimited number of "used-defined kinds" can be added in configuration. +--------------+---------------------------------------------------------------------------------+ -| **Endpoint** | **Explanation** | -+--------------+---------------------------------------------------------------------------------+ -| doh | :ref:`mod-http-doh` | +| **Kind** | **Explanation** | +--------------+---------------------------------------------------------------------------------+ | webmgmt | :ref:`built-in web management ` APIs (includes DoH) | +--------------+---------------------------------------------------------------------------------+ +| doh | :ref:`mod-http-doh` | ++--------------+---------------------------------------------------------------------------------+ Each network address and port combination can be configured to expose one kind of endpoint. This is done using the same mechanisms as @@ -124,6 +124,21 @@ they currently won't be shared. It's assumed that you don't want a self-signed certificate for serious deployments anyway. +.. _mod-http-doh: + +Legacy DNS-over-HTTPS (DoH) +--------------------------- + +.. warning:: The legacy DoH implementation using ``http`` module (``kind='doh'``) + is deprecated. It has known performance and stability issues that won't be fixed. + Use new :ref:`dns-over-https` implementation instead. + +This was an experimental implementation of :rfc:`8484`. It was configured using +``doh`` kind in :func:`net.listen`. Its configuration (such as certificates) +took place in ``http.config()``. + +Queries were served on ``/doh`` and ``/dns-query`` endpoints. + .. _mod-http-built-in-services: Built-in services @@ -137,7 +152,8 @@ "``/stats``", "Statistics/metrics", "Exported :ref:`metrics ` from :ref:`mod-stats` in JSON format." "``/metrics``", "Prometheus metrics", "Exported metrics for Prometheus_." "``/trace/:name/:type``", "Tracking", ":ref:`Trace resolution ` of a DNS query and return the verbose logs." - "``/doh``", "DNS-over-HTTP", ":rfc:`8484` endpoint, see :ref:`mod-http-doh`." + "``/doh``", "Legacy DNS-over-HTTPS", ":rfc:`8484` endpoint, see :ref:`mod-http-doh`." + "``/dns-query``", "Legacy DNS-over-HTTPS", ":rfc:`8484` endpoint, see :ref:`mod-http-doh`." Dependencies ------------ @@ -152,17 +168,17 @@ $ brew install openssl $ brew link openssl --force # Override system OpenSSL - Any other system can install from LuaRocks directly: + Some other systems can install from LuaRocks directly: .. code-block:: bash - $ luarocks install http + $ luarocks --lua-version 5.1 install http * (*optional*) `mmdblua `_ available in LuaRocks .. code-block:: bash - $ luarocks install --server=https://luarocks.org/dev mmdblua + $ luarocks --lua-version 5.1 install --server=https://luarocks.org/dev mmdblua $ curl -O https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz $ gzip -d GeoLite2-City.mmdb.gz diff -Nru knot-resolver-5.1.1/doc/modules-policy.rst knot-resolver-5.2.1/doc/modules-policy.rst --- knot-resolver-5.1.1/doc/modules-policy.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/modules-policy.rst 2020-12-09 09:44:29.000000000 +0000 @@ -110,10 +110,10 @@ .. code-block:: lua - -- Whitelist 'www.badboy.cz' - policy.add(policy.pattern(policy.PASS, todname('www.badboy.cz.'))) - -- Block all names below badboy.cz - policy.add(policy.suffix(policy.DENY, {todname('badboy.cz.')})) + -- Whitelist 'good.example.com' + policy.add(policy.pattern(policy.PASS, todname('good.example.com.'))) + -- Block all names below example.com + policy.add(policy.suffix(policy.DENY, {todname('example.com.')})) .. py:attribute:: DENY @@ -141,19 +141,43 @@ .. code-block:: lua - -- this policy is enforced on answers - -- therefore we have to use 'postrule' - -- (the "true" at the end of policy.add) - policy.add(policy.REROUTE({'192.0.2.0/24', '127.0.0.0'}), true) + -- this policy is enforced on answers + -- therefore we have to use 'postrule' + -- (the "true" at the end of policy.add) + policy.add(policy.REROUTE({'192.0.2.0/24', '127.0.0.0'}), true) + +.. function:: ANSWER({ type = { rdata=data, [ttl=1] } }, [nodata=false]) + + Overwrite Resource Records in responses with specified values. + + * type + - RR type to be replaced, e.g. ``[kres.type.A]`` or `numeric value `_. + * rdata + - RR data in DNS wire format, i.e. binary form specific for given RR type. Set of multiple RRs can be specified as table ``{ rdata1, rdata2, ... }``. Use helper function :func:`kres.str2ip` to generate wire format for A and AAAA records. + * ttl + - TTL in seconds. Default: 1 second. + * nodata + - If type requested by client is not configured in this policy: -.. function:: ANSWER({ type = { ttl=ttl, rdata=data} }, nodata) + - ``true``: Return empty answer (`NODATA`). + - ``false``: Ignore this policy and continue processing other rules. - Overwrite rr data in response. ``rdata`` takes just IP address. If `nodata` is `true` policy return `NODATA` when requested type from client isn't specified (default: ``nodata=false``). + Default: ``false``. .. code-block:: lua - -- this policy changes IPv4 adress and TTL for `exmaple.com` - policy.add(policy.suffix(policy.ANSWER({ [kres.type.A] = { ttl=300, rdata='\192\0\2\7' } }), { todname('example.com') })) + -- policy to change IPv4 address and TTL for example.com + policy.add( + policy.suffix( + policy.ANSWER( + { [kres.type.A] = { rdata=kres.str2ip('192.0.2.7'), ttl=300 } } + ), { todname('example.com') })) + -- policy to generate two TXT records (specified in binary format) for example.net + policy.add( + policy.suffix( + policy.ANSWER( + { [kres.type.TXT] = { rdata={'\005first', '\006second'}, ttl=5 } } + ), { todname('example.net') })) More complex non-chain actions are described in their own chapters, namely: @@ -171,7 +195,7 @@ .. code-block:: lua - policy.add(policy.all(policy.MIRROR('127.0.0.2'))) + policy.add(policy.all(policy.MIRROR('127.0.0.2'))) .. function:: FLAGS(set, clear) @@ -183,9 +207,9 @@ .. code-block:: lua - -- log answers from all authoritative servers involved in resolving - -- requests for example.net. and its subdomains - policy.add(policy.suffix(policy.QTRACE, policy.todnames({'example.net'}))) + -- log answers from all authoritative servers involved in resolving + -- requests for example.net. and its subdomains + policy.add(policy.suffix(policy.QTRACE, policy.todnames({'example.net'}))) .. py:attribute:: REQTRACE @@ -201,9 +225,9 @@ .. code-block:: lua - policy.add(policy.suffix( - policy.DEBUG_CACHE_MISS, - policy.todnames({'example.com.'}))) + policy.add(policy.suffix( + policy.DEBUG_CACHE_MISS, + policy.todnames({'example.com.'}))) .. py:function:: DEBUG_IF(test_function) @@ -219,11 +243,11 @@ .. code-block:: lua - policy.add(policy.suffix( - policy.DEBUG_IF(function(req) - return (req.state ~= kres.DONE) - end), - policy.todnames({'dnssec-failed.org.'}))) + policy.add(policy.suffix( + policy.DEBUG_IF(function(req) + return (req.state ~= kres.DONE) + end), + policy.todnames({'dnssec-failed.org.'}))) Custom actions @@ -239,20 +263,21 @@ .. code-block:: lua - -- Custom action which generates fake A record - local ffi = require('ffi') - local function fake_A_record(state, req) - local answer = req.answer - local qry = req:current() - if qry.stype ~= kres.type.A then - return state - end - ffi.C.kr_pkt_make_auth_header(answer) - answer:rcode(kres.rcode.NOERROR) - answer:begin(kres.section.ANSWER) - answer:put(qry.sname, 900, answer:qclass(), kres.type.A, '\192\168\1\3') - return kres.DONE - end + -- Custom action which generates fake A record + local ffi = require('ffi') + local function fake_A_record(state, req) + local answer = req:ensure_answer() + if answer == nil then return nil end + local qry = req:current() + if qry.stype ~= kres.type.A then + return state + end + ffi.C.kr_pkt_make_auth_header(answer) + answer:rcode(kres.rcode.NOERROR) + answer:begin(kres.section.ANSWER) + answer:put(qry.sname, 900, answer:qclass(), kres.type.A, '\192\168\1\3') + return kres.DONE + end This custom action can be used as any other built-in action. For example this applies our *fake A record action* and executes it on all queries in subtree ``example.net``: @@ -281,13 +306,11 @@ .. code-block:: lua - -- Forward all queries to public resolvers https://www.nic.cz/odvr - policy.add(policy.all( - policy.FORWARD( - {'2001:148f:fffe::1', '2001:148f:ffff::1', - '185.43.135.1', '193.14.47.1'}))) - - + -- Forward all queries to public resolvers https://www.nic.cz/odvr + policy.add(policy.all( + policy.FORWARD( + {'2001:148f:fffe::1', '2001:148f:ffff::1', + '185.43.135.1', '193.14.47.1'}))) A variant which uses encrypted DNS-over-TLS transport is called :func:`policy.TLS_FORWARD`, please see section :ref:`tls-forwarding`. @@ -302,13 +325,12 @@ .. code-block:: lua - -- Answers for reverse queries about the 192.168.1.0/24 subnet - -- are to be obtained from IP address 192.0.2.1 port 5353 - -- This disables DNSSEC validation! - policy.add(policy.suffix( - policy.STUB('192.0.2.1@5353'), - {todname('1.168.192.in-addr.arpa')})) - + -- Answers for reverse queries about the 192.168.1.0/24 subnet + -- are to be obtained from IP address 192.0.2.1 port 5353 + -- This disables DNSSEC validation! + policy.add(policy.suffix( + policy.STUB('192.0.2.1@5353'), + {todname('1.168.192.in-addr.arpa')})) .. _tls-forwarding: @@ -342,8 +364,8 @@ .. code-block:: lua - policy.TLS_FORWARD({ - {'2001:DB8::d0c', hostname='res.example.com'}}) + policy.TLS_FORWARD({ + {'2001:DB8::d0c', hostname='res.example.com'}}) - ``hostname`` must be a valid domain name matching server's certificate. It will also be sent to the server as SNI_. - ``ca_file`` optionally contains a path to a CA certificate (or certificate bundle) in `PEM format`_. @@ -363,24 +385,24 @@ .. code-block:: lua - modules = { 'policy' } - -- forward all queries over TLS to the specified server - policy.add(policy.all(policy.TLS_FORWARD({{'192.0.2.1', pin_sha256='YQ=='}}))) - -- for brevity, other TLS examples omit policy.add(policy.all()) - -- single server authenticated using its certificate pin_sha256 - policy.TLS_FORWARD({{'192.0.2.1', pin_sha256='YQ=='}}) -- pin_sha256 is base64-encoded - -- single server authenticated using hostname and system-wide CA certificates - policy.TLS_FORWARD({{'192.0.2.1', hostname='res.example.com'}}) - -- single server using non-standard port - policy.TLS_FORWARD({{'192.0.2.1@443', pin_sha256='YQ=='}}) -- use @ or # to specify port - -- single server with multiple valid pins (e.g. anycast) - policy.TLS_FORWARD({{'192.0.2.1', pin_sha256={'YQ==', 'Wg=='}}) - -- multiple servers, each with own authenticator - policy.TLS_FORWARD({ -- please note that { here starts list of servers - {'192.0.2.1', pin_sha256='Wg=='}, - -- server must present certificate issued by specified CA and hostname must match - {'2001:DB8::d0c', hostname='res.example.com', ca_file='/etc/knot-resolver/tlsca.crt'} - }) + modules = { 'policy' } + -- forward all queries over TLS to the specified server + policy.add(policy.all(policy.TLS_FORWARD({{'192.0.2.1', pin_sha256='YQ=='}}))) + -- for brevity, other TLS examples omit policy.add(policy.all()) + -- single server authenticated using its certificate pin_sha256 + policy.TLS_FORWARD({{'192.0.2.1', pin_sha256='YQ=='}}) -- pin_sha256 is base64-encoded + -- single server authenticated using hostname and system-wide CA certificates + policy.TLS_FORWARD({{'192.0.2.1', hostname='res.example.com'}}) + -- single server using non-standard port + policy.TLS_FORWARD({{'192.0.2.1@443', pin_sha256='YQ=='}}) -- use @ or # to specify port + -- single server with multiple valid pins (e.g. anycast) + policy.TLS_FORWARD({{'192.0.2.1', pin_sha256={'YQ==', 'Wg=='}}) + -- multiple servers, each with own authenticator + policy.TLS_FORWARD({ -- please note that { here starts list of servers + {'192.0.2.1', pin_sha256='Wg=='}, + -- server must present certificate issued by specified CA and hostname must match + {'2001:DB8::d0c', hostname='res.example.com', ca_file='/etc/knot-resolver/tlsca.crt'} + }) Forwarding to multiple targets ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -502,17 +524,17 @@ The easiest work-around is to disable reading from cache for grafted domains. .. code-block:: lua - :caption: Example configuration grafting domains onto public DNS namespace + :caption: Example configuration grafting domains onto public DNS namespace - extraTrees = policy.todnames( - {'faketldtest.', - 'sld.example.', - 'internal.example.com.', - '2.0.192.in-addr.arpa.' -- this applies to reverse DNS tree as well - }) - -- Beware: the rule order is important, as STUB is not a chain action. - policy.add(policy.suffix(policy.FLAGS({'NO_CACHE'}), extraTrees)) - policy.add(policy.suffix(policy.STUB({'2001:db8::1'}), extraTrees)) + extraTrees = policy.todnames( + {'faketldtest.', + 'sld.example.', + 'internal.example.com.', + '2.0.192.in-addr.arpa.' -- this applies to reverse DNS tree as well + }) + -- Beware: the rule order is important, as STUB is not a chain action. + policy.add(policy.suffix(policy.FLAGS({'NO_CACHE'}), extraTrees)) + policy.add(policy.suffix(policy.STUB({'2001:db8::1'}), extraTrees)) Response policy zones --------------------- @@ -572,7 +594,7 @@ .. code-block:: lua - policy.add( + policy.add( policy.rpz(policy.DENY_MSG('domain blocked by your resolver operator'), '/etc/knot-resolver/blocklist.rpz', true)) @@ -600,10 +622,10 @@ .. code-block:: lua - -- mirror all queriesm, keep handle so we can retrieve information later - local rule = policy.add(policy.all(policy.MIRROR('127.0.0.2'))) - -- we can print statistics about this rule any time later - print(string.format('id: %d, matched queries: %d', rule.id, rule.count) + -- mirror all queriesm, keep handle so we can retrieve information later + local rule = policy.add(policy.all(policy.MIRROR('127.0.0.2'))) + -- we can print statistics about this rule any time later + print(string.format('id: %d, matched queries: %d', rule.id, rule.count) .. function:: del(id) diff -Nru knot-resolver-5.1.1/doc/modules-stats.rst knot-resolver-5.2.1/doc/modules-stats.rst --- knot-resolver-5.1.1/doc/modules-stats.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/modules-stats.rst 2020-12-09 09:44:29.000000000 +0000 @@ -44,6 +44,9 @@ | request.doh | external requests received over | | | DNS-over-HTTP (:rfc:`8484`) | +------------------+----------------------------------------------+ +| request.xdp | external requests received over plain UDP | +| | via an AF_XDP socket | ++------------------+----------------------------------------------+ +----------------------------------------------------+ | **Global answer counters** | diff -Nru knot-resolver-5.1.1/doc/NEWS.rst knot-resolver-5.2.1/doc/NEWS.rst --- knot-resolver-5.1.1/doc/NEWS.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/NEWS.rst 2020-12-09 09:44:29.000000000 +0000 @@ -6,5 +6,31 @@ Release notes ************* +Version numbering +================= +Version number format is ``major.minor.patch``. +Knot Resolver does not use semantic versioning even though the version number looks similar. + +Leftmost number which was changed signalizes what to expect when upgrading: + +Major version + * Manual upgrade steps might be necessary, please follow instructions in :ref:`Upgrading` section. + * Major releases may contain significant changes including changes to configuration format. + * We might release a new major also when internal implementation details change significantly. + +Minor version + * Configuration stays compatible with the previous version, except for undocumented or very obscure options. + * Upgrade should be seamless for users who use modules shipped as part of Knot Resolver distribution. + * Incompatible changes in internal APIs are allowed in minor versions. Users who develop or use custom modules + (i.e. modules not distributed together with Knot Resolver) need to double check their modules for incompatibilities. + :ref:`Upgrading` section should contain hints for module authors. + +Patch version + * Everything should be compatible with the previous version. + * API for modules should be stable on best effort basis, i.e. API is very unlikely to break in patch releases. + * Custom modules might need to be recompiled, i.e. ABI compatibility is not guaranteed. + +This definition is not applicable to versions older than 5.2.0. + .. include:: ../NEWS diff -Nru knot-resolver-5.1.1/doc/packaging/debian/10/builddeps knot-resolver-5.2.1/doc/packaging/debian/10/builddeps --- knot-resolver-5.1.1/doc/packaging/debian/10/builddeps 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/packaging/debian/10/builddeps 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -doxygen -python3-sphinx -python3-breathe -python3-sphinx-rtd-theme diff -Nru knot-resolver-5.1.1/doc/packaging/debian/10/build.sh knot-resolver-5.2.1/doc/packaging/debian/10/build.sh --- knot-resolver-5.1.1/doc/packaging/debian/10/build.sh 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/packaging/debian/10/build.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-3.0-or-later -ninja -C build_packaging doc diff -Nru knot-resolver-5.1.1/doc/packaging/test.sh knot-resolver-5.2.1/doc/packaging/test.sh --- knot-resolver-5.1.1/doc/packaging/test.sh 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/packaging/test.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-3.0-or-later -test -e doc/html/index.html diff -Nru knot-resolver-5.1.1/doc/.packaging/debian/10/builddeps knot-resolver-5.2.1/doc/.packaging/debian/10/builddeps --- knot-resolver-5.1.1/doc/.packaging/debian/10/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/debian/10/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +doxygen +python3-sphinx +python3-breathe +python3-sphinx-rtd-theme diff -Nru knot-resolver-5.1.1/doc/.packaging/debian/10/build.sh knot-resolver-5.2.1/doc/.packaging/debian/10/build.sh --- knot-resolver-5.1.1/doc/.packaging/debian/10/build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/debian/10/build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,19 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +[ -d /root/kresd/build_packaging ] && rm -rf /root/kresd/build_packaging/; +CFLAGS="$CFLAGS -Wall -pedantic -fno-omit-frame-pointer" +LDFLAGS="$LDFLAGS -Wl,--as-needed" +meson build_packaging \ + --buildtype=plain \ + --prefix=/root/kresd/install_packaging \ + --libdir=lib \ + --default-library=static \ + -Ddoc=enabled \ + -Dsystemd_files=enabled \ + -Dclient=enabled \ + -Dkeyfile_default=/usr/share/dns/root.key \ + -Droot_hints=/usr/share/dns/root.hints \ + -Dinstall_kresd_conf=enabled \ + -Dunit_tests=enabled \ + -Dc_args="${CFLAGS}" \ + -Dc_link_args="${LDFLAGS}"; diff -Nru knot-resolver-5.1.1/doc/.packaging/debian/10/install.sh knot-resolver-5.2.1/doc/.packaging/debian/10/install.sh --- knot-resolver-5.1.1/doc/.packaging/debian/10/install.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/debian/10/install.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +ninja -C build_packaging doc diff -Nru knot-resolver-5.1.1/doc/.packaging/debian/9/builddeps knot-resolver-5.2.1/doc/.packaging/debian/9/builddeps --- knot-resolver-5.1.1/doc/.packaging/debian/9/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/debian/9/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +doxygen +python3-sphinx +python3-breathe +python3-sphinx-rtd-theme diff -Nru knot-resolver-5.1.1/doc/.packaging/debian/9/build.sh knot-resolver-5.2.1/doc/.packaging/debian/9/build.sh --- knot-resolver-5.1.1/doc/.packaging/debian/9/build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/debian/9/build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,19 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +[ -d /root/kresd/build_packaging ] && rm -rf /root/kresd/build_packaging/; +CFLAGS="$CFLAGS -Wall -pedantic -fno-omit-frame-pointer" +LDFLAGS="$LDFLAGS -Wl,--as-needed" +meson build_packaging \ + --buildtype=plain \ + --prefix=/root/kresd/install_packaging \ + --libdir=lib \ + --default-library=static \ + -Ddoc=enabled \ + -Dsystemd_files=enabled \ + -Dclient=enabled \ + -Dkeyfile_default=/usr/share/dns/root.key \ + -Droot_hints=/usr/share/dns/root.hints \ + -Dinstall_kresd_conf=enabled \ + -Dunit_tests=enabled \ + -Dc_args="${CFLAGS}" \ + -Dc_link_args="${LDFLAGS}"; diff -Nru knot-resolver-5.1.1/doc/.packaging/debian/9/install.sh knot-resolver-5.2.1/doc/.packaging/debian/9/install.sh --- knot-resolver-5.1.1/doc/.packaging/debian/9/install.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/debian/9/install.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +ninja -C build_packaging doc diff -Nru knot-resolver-5.1.1/doc/.packaging/fedora/31/builddeps knot-resolver-5.2.1/doc/.packaging/fedora/31/builddeps --- knot-resolver-5.1.1/doc/.packaging/fedora/31/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/fedora/31/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +doxygen +python3-sphinx +python3-breathe +python3-sphinx_rtd_theme diff -Nru knot-resolver-5.1.1/doc/.packaging/fedora/31/build.sh knot-resolver-5.2.1/doc/.packaging/fedora/31/build.sh --- knot-resolver-5.1.1/doc/.packaging/fedora/31/build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/fedora/31/build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,20 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +[ -d /root/kresd/build_packaging ] && rm -rf /root/kresd/build_packaging/; +CFLAGS="$CFLAGS -Wall -pedantic -fno-omit-frame-pointer" +LDFLAGS="$LDFLAGS -Wl,--as-needed" +meson build_packaging \ + --buildtype=plain \ + --prefix=/root/kresd/install_packaging \ + --sbindir=sbin \ + --libdir=lib \ + --includedir=include \ + --sysconfdir=etc \ + -Ddoc=enabled \ + -Dsystemd_files=enabled \ + -Dclient=enabled \ + -Dunit_tests=enabled \ + -Dmanaged_ta=enabled \ + -Dkeyfile_default=/var/lib/knot-resolver/root.keys \ + -Dinstall_root_keys=enabled \ + -Dinstall_kresd_conf=enabled; diff -Nru knot-resolver-5.1.1/doc/.packaging/fedora/31/install.sh knot-resolver-5.2.1/doc/.packaging/fedora/31/install.sh --- knot-resolver-5.1.1/doc/.packaging/fedora/31/install.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/fedora/31/install.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +ninja -C build_packaging doc diff -Nru knot-resolver-5.1.1/doc/.packaging/fedora/32/30/builddeps knot-resolver-5.2.1/doc/.packaging/fedora/32/30/builddeps --- knot-resolver-5.1.1/doc/.packaging/fedora/32/30/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/fedora/32/30/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +doxygen +python3-sphinx +python3-breathe +python3-sphinx_rtd_theme diff -Nru knot-resolver-5.1.1/doc/.packaging/fedora/32/30/build.sh knot-resolver-5.2.1/doc/.packaging/fedora/32/30/build.sh --- knot-resolver-5.1.1/doc/.packaging/fedora/32/30/build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/fedora/32/30/build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,20 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +[ -d /root/kresd/build_packaging ] && rm -rf /root/kresd/build_packaging/; +CFLAGS="$CFLAGS -Wall -pedantic -fno-omit-frame-pointer" +LDFLAGS="$LDFLAGS -Wl,--as-needed" +meson build_packaging \ + --buildtype=plain \ + --prefix=/root/kresd/install_packaging \ + --sbindir=sbin \ + --libdir=lib \ + --includedir=include \ + --sysconfdir=etc \ + -Ddoc=enabled \ + -Dsystemd_files=enabled \ + -Dclient=enabled \ + -Dunit_tests=enabled \ + -Dmanaged_ta=enabled \ + -Dkeyfile_default=/var/lib/knot-resolver/root.keys \ + -Dinstall_root_keys=enabled \ + -Dinstall_kresd_conf=enabled; diff -Nru knot-resolver-5.1.1/doc/.packaging/fedora/32/30/install.sh knot-resolver-5.2.1/doc/.packaging/fedora/32/30/install.sh --- knot-resolver-5.1.1/doc/.packaging/fedora/32/30/install.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/fedora/32/30/install.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +ninja -C build_packaging doc diff -Nru knot-resolver-5.1.1/doc/.packaging/fedora/32/builddeps knot-resolver-5.2.1/doc/.packaging/fedora/32/builddeps --- knot-resolver-5.1.1/doc/.packaging/fedora/32/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/fedora/32/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +doxygen +python3-sphinx +python3-breathe +python3-sphinx_rtd_theme diff -Nru knot-resolver-5.1.1/doc/.packaging/fedora/32/build.sh knot-resolver-5.2.1/doc/.packaging/fedora/32/build.sh --- knot-resolver-5.1.1/doc/.packaging/fedora/32/build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/fedora/32/build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,20 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +[ -d /root/kresd/build_packaging ] && rm -rf /root/kresd/build_packaging/; +CFLAGS="$CFLAGS -Wall -pedantic -fno-omit-frame-pointer" +LDFLAGS="$LDFLAGS -Wl,--as-needed" +meson build_packaging \ + --buildtype=plain \ + --prefix=/root/kresd/install_packaging \ + --sbindir=sbin \ + --libdir=lib \ + --includedir=include \ + --sysconfdir=etc \ + -Ddoc=enabled \ + -Dsystemd_files=enabled \ + -Dclient=enabled \ + -Dunit_tests=enabled \ + -Dmanaged_ta=enabled \ + -Dkeyfile_default=/var/lib/knot-resolver/root.keys \ + -Dinstall_root_keys=enabled \ + -Dinstall_kresd_conf=enabled; diff -Nru knot-resolver-5.1.1/doc/.packaging/fedora/32/install.sh knot-resolver-5.2.1/doc/.packaging/fedora/32/install.sh --- knot-resolver-5.1.1/doc/.packaging/fedora/32/install.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/fedora/32/install.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +ninja -C build_packaging doc diff -Nru knot-resolver-5.1.1/doc/.packaging/leap/15.2/builddeps knot-resolver-5.2.1/doc/.packaging/leap/15.2/builddeps --- knot-resolver-5.1.1/doc/.packaging/leap/15.2/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/leap/15.2/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +doxygen +python3-Sphinx +python3-breathe +python3-sphinx_rtd_theme diff -Nru knot-resolver-5.1.1/doc/.packaging/leap/15.2/build.sh knot-resolver-5.2.1/doc/.packaging/leap/15.2/build.sh --- knot-resolver-5.1.1/doc/.packaging/leap/15.2/build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/leap/15.2/build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,20 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +[ -d /root/kresd/build_packaging ] && rm -rf /root/kresd/build_packaging/; +CFLAGS="$CFLAGS -Wall -pedantic -fno-omit-frame-pointer" +LDFLAGS="$LDFLAGS -Wl,--as-needed" +meson build_packaging \ + --buildtype=plain \ + --prefix=/root/kresd/install_packaging \ + --sbindir=sbin \ + --libdir=lib \ + --includedir=include \ + --sysconfdir=etc \ + -Ddoc=enabled \ + -Dsystemd_files=enabled \ + -Dclient=enabled \ + -Dunit_tests=enabled \ + -Dmanaged_ta=enabled \ + -Dkeyfile_default=/var/lib/knot-resolver/root.keys \ + -Dinstall_root_keys=enabled \ + -Dinstall_kresd_conf=enabled; diff -Nru knot-resolver-5.1.1/doc/.packaging/leap/15.2/install.sh knot-resolver-5.2.1/doc/.packaging/leap/15.2/install.sh --- knot-resolver-5.1.1/doc/.packaging/leap/15.2/install.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/leap/15.2/install.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +ninja -C build_packaging doc diff -Nru knot-resolver-5.1.1/doc/.packaging/test.sh knot-resolver-5.2.1/doc/.packaging/test.sh --- knot-resolver-5.1.1/doc/.packaging/test.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/test.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +test -e ../doc/html/index.html diff -Nru knot-resolver-5.1.1/doc/.packaging/ubuntu/16.04/builddeps knot-resolver-5.2.1/doc/.packaging/ubuntu/16.04/builddeps --- knot-resolver-5.1.1/doc/.packaging/ubuntu/16.04/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/ubuntu/16.04/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +doxygen +python3-sphinx +python3-breathe +python3-sphinx-rtd-theme diff -Nru knot-resolver-5.1.1/doc/.packaging/ubuntu/16.04/build.sh knot-resolver-5.2.1/doc/.packaging/ubuntu/16.04/build.sh --- knot-resolver-5.1.1/doc/.packaging/ubuntu/16.04/build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/ubuntu/16.04/build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,19 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +[ -d /root/kresd/build_packaging ] && rm -rf /root/kresd/build_packaging/; +CFLAGS="$CFLAGS -Wall -pedantic -fno-omit-frame-pointer" +LDFLAGS="$LDFLAGS -Wl,--as-needed" +meson build_packaging \ + --buildtype=plain \ + --prefix=/root/kresd/install_packaging \ + --libdir=lib \ + --default-library=static \ + -Ddoc=enabled \ + -Dsystemd_files=enabled \ + -Dclient=enabled \ + -Dkeyfile_default=/usr/share/dns/root.key \ + -Droot_hints=/usr/share/dns/root.hints \ + -Dinstall_kresd_conf=enabled \ + -Dunit_tests=enabled \ + -Dc_args="${CFLAGS}" \ + -Dc_link_args="${LDFLAGS}"; diff -Nru knot-resolver-5.1.1/doc/.packaging/ubuntu/16.04/install.sh knot-resolver-5.2.1/doc/.packaging/ubuntu/16.04/install.sh --- knot-resolver-5.1.1/doc/.packaging/ubuntu/16.04/install.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/ubuntu/16.04/install.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +ninja -C build_packaging doc diff -Nru knot-resolver-5.1.1/doc/.packaging/ubuntu/18.04/builddeps knot-resolver-5.2.1/doc/.packaging/ubuntu/18.04/builddeps --- knot-resolver-5.1.1/doc/.packaging/ubuntu/18.04/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/ubuntu/18.04/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +doxygen +python3-sphinx +python3-breathe +python3-sphinx-rtd-theme diff -Nru knot-resolver-5.1.1/doc/.packaging/ubuntu/18.04/build.sh knot-resolver-5.2.1/doc/.packaging/ubuntu/18.04/build.sh --- knot-resolver-5.1.1/doc/.packaging/ubuntu/18.04/build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/ubuntu/18.04/build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,19 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +[ -d /root/kresd/build_packaging ] && rm -rf /root/kresd/build_packaging/; +CFLAGS="$CFLAGS -Wall -pedantic -fno-omit-frame-pointer" +LDFLAGS="$LDFLAGS -Wl,--as-needed" +meson build_packaging \ + --buildtype=plain \ + --prefix=/root/kresd/install_packaging \ + --libdir=lib \ + --default-library=static \ + -Ddoc=enabled \ + -Dsystemd_files=enabled \ + -Dclient=enabled \ + -Dkeyfile_default=/usr/share/dns/root.key \ + -Droot_hints=/usr/share/dns/root.hints \ + -Dinstall_kresd_conf=enabled \ + -Dunit_tests=enabled \ + -Dc_args="${CFLAGS}" \ + -Dc_link_args="${LDFLAGS}"; diff -Nru knot-resolver-5.1.1/doc/.packaging/ubuntu/18.04/install.sh knot-resolver-5.2.1/doc/.packaging/ubuntu/18.04/install.sh --- knot-resolver-5.1.1/doc/.packaging/ubuntu/18.04/install.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/ubuntu/18.04/install.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +ninja -C build_packaging doc diff -Nru knot-resolver-5.1.1/doc/.packaging/ubuntu/20.04/builddeps knot-resolver-5.2.1/doc/.packaging/ubuntu/20.04/builddeps --- knot-resolver-5.1.1/doc/.packaging/ubuntu/20.04/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/ubuntu/20.04/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +doxygen +python3-sphinx +python3-breathe +python3-sphinx-rtd-theme diff -Nru knot-resolver-5.1.1/doc/.packaging/ubuntu/20.04/build.sh knot-resolver-5.2.1/doc/.packaging/ubuntu/20.04/build.sh --- knot-resolver-5.1.1/doc/.packaging/ubuntu/20.04/build.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/ubuntu/20.04/build.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,19 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +[ -d /root/kresd/build_packaging ] && rm -rf /root/kresd/build_packaging/; +CFLAGS="$CFLAGS -Wall -pedantic -fno-omit-frame-pointer" +LDFLAGS="$LDFLAGS -Wl,--as-needed" +meson build_packaging \ + --buildtype=plain \ + --prefix=/root/kresd/install_packaging \ + --libdir=lib \ + --default-library=static \ + -Ddoc=enabled \ + -Dsystemd_files=enabled \ + -Dclient=enabled \ + -Dkeyfile_default=/usr/share/dns/root.key \ + -Droot_hints=/usr/share/dns/root.hints \ + -Dinstall_kresd_conf=enabled \ + -Dunit_tests=enabled \ + -Dc_args="${CFLAGS}" \ + -Dc_link_args="${LDFLAGS}"; diff -Nru knot-resolver-5.1.1/doc/.packaging/ubuntu/20.04/install.sh knot-resolver-5.2.1/doc/.packaging/ubuntu/20.04/install.sh --- knot-resolver-5.1.1/doc/.packaging/ubuntu/20.04/install.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/doc/.packaging/ubuntu/20.04/install.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +ninja -C build_packaging doc diff -Nru knot-resolver-5.1.1/doc/quickstart-config.rst knot-resolver-5.2.1/doc/quickstart-config.rst --- knot-resolver-5.1.1/doc/quickstart-config.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/quickstart-config.rst 2020-12-09 09:44:29.000000000 +0000 @@ -22,7 +22,7 @@ Easiest way to configure Knot Resolver is to paste your configuration into configuration file ``/etc/knot-resolver/kresd.conf``. Complete configurations files for examples in this chapter -can be found `here `_. +can be found `here `_. The example configuration files are also installed as documentation files, typically in directory ``/usr/share/doc/knot-resolver/examples/`` (their location may be different based on your Linux distribution). Detailed configuration of daemon and implemented modules can be found in configuration reference: diff -Nru knot-resolver-5.1.1/doc/systemd-multiinst.rst knot-resolver-5.2.1/doc/systemd-multiinst.rst --- knot-resolver-5.1.1/doc/systemd-multiinst.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/systemd-multiinst.rst 2020-12-09 09:44:29.000000000 +0000 @@ -93,7 +93,7 @@ elseif string.match(systemd_instance, '^tls') then net.listen('127.0.0.1', 853, { kind = 'tls' }) elseif string.match(systemd_instance, '^doh') then - net.listen('127.0.0.1', 443, { kind = 'doh' }) + net.listen('127.0.0.1', 443, { kind = 'doh2' }) else panic("Use kresd@dns*, kresd@tls* or kresd@doh* instance names") end diff -Nru knot-resolver-5.1.1/doc/upgrading.rst knot-resolver-5.2.1/doc/upgrading.rst --- knot-resolver-5.1.1/doc/upgrading.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/doc/upgrading.rst 2020-12-09 09:44:29.000000000 +0000 @@ -6,18 +6,85 @@ Upgrading ********* -This section summarizes steps required for upgrade to newer Knot Resolver versions. +This section summarizes steps required when upgrading to newer Knot Resolver versions. We advise users to also read :ref:`release_notes` for respective versions. Section *Module changes* is relevant only for users who develop or use third-party modules. + +Upcoming changes +================ + +Following section provides information about selected changes in not-yet-released versions. +We advise users to prepare for these changes sooner rather than later to make it easier to upgrade to +newer versions when they are released. + +* Going forward DNS-over-HTTP (DoH) will be supported only over HTTP/2 with TLS. + This limitation allows us to provide a new :ref:`more reliable and scalable implementation + of DoH ` (``kind='doh2'``). +* Command line option ``--forks`` (``-f``) `is deprecated and will be eventually removed + `_. + Preferred way to manage :ref:`systemd-multiple-instances` is to use a process manager, + e.g. systemd_ or supervisord_. + +.. _`systemd`: https://systemd.io/ +.. _`supervisord`: http://supervisord.org/ + + +5.1 to 5.2 +========== + +Users +----- + +* DoH over HTTP/1 and unencrypted transports is still available in + :ref:`legacy http module ` (``kind='doh'``). + This module will not receive receive any more bugfixes and will be eventually removed. +* Users of :ref:`control-sockets` API need to terminate each command sent to resolver with newline + character (ASCII ``\n``). Correct usage: ``cache.stats()\n``. + Newline terminated commands are accepted by all resolver versions >= 1.0.0. +* `DNS Flag Day 2020 `_ is now effective and Knot Resolver uses + maximum size of UDP answer to 1232 bytes. Please double-check your firewall, + it has to allow DNS traffic on UDP and **also TCP** port 53. +* Human readable output in interactive mode and from :ref:`control-sockets` was improved and + as consequence slightly changed its format. Users who need machine readable output for scripts + should use Lua function ``tojson()`` to convert Lua values into standard JSON format instead + of attempting to parse the human readable output. + For example API call ``tojson(cache.stats())\n`` will return JSON string with ``cache.stats()`` + results represented as dictionary. + Function ``tojson()`` is available in all resolver versions >= 1.0.0. + +Configuration file +------------------ + +* Statistics exporter :ref:`mod-graphite` now uses default prefix which combines + :func:`hostname()` and :envvar:`worker.id` instead of bare :func:`hostname()`. + This prevents :ref:`systemd-multiple-instances` from sending + conflicting statistics to server. In case you want to continue in previous time series you + can manually set the old values using option ``prefix`` + in :ref:`Graphite configuration `. + Beware that non-default values require careful + :ref:`instance-specific-configuration` to avoid conflicting names. +* Lua variable :envvar:`worker.id` is now a string with either Systemd instance name or PID + (instead of number). If your custom configuration uses :envvar:`worker.id` value please + check your scripts. + +Module changes +-------------- +* Reply packet :c:type:`kr_request.answer` + `is not allocated `_ + immediately when the request comes. + See the new :c:func:`kr_request_ensure_answer` function, + wrapped for lua as ``req:ensure_answer()``. + + 5.0 to 5.1 ========== Module changes -------------- -* Modules which use :c:type:`kr_request.trace_log` handler need update to modified handler API. Example migration is `modules/watchdog/watchdog.lua `_. -* Modules which were using logger :c:func:`kr_log_qverbose_impl` need migration to new logger :c:func:`kr_log_q`. Example migration is `modules/rebinding/rebinding.lua `_. +* Modules which use :c:type:`kr_request.trace_log` handler need update to modified handler API. Example migration is `modules/watchdog/watchdog.lua `_. +* Modules which were using logger :c:func:`kr_log_qverbose_impl` need migration to new logger :c:func:`kr_log_q`. Example migration is `modules/rebinding/rebinding.lua `_. * Modules which were using :c:func:`kr_ranked_rrarray_add` should note that on success it no longer returns exclusively zero but index into the array (non-negative). Error states are unchanged (negative). @@ -37,7 +104,7 @@ * ``-f`` / ``--forks`` command-line option is deprecated. In case you just want to trigger non-interactive mode, there's new ``-n`` / ``--noninteractive``. - This forking style `was not ergonomic `_; + This forking style `was not ergonomic `_; with independent kresd processes you can better utilize a process manager (e.g. systemd). @@ -46,7 +113,7 @@ * Network interface are now configured in ``kresd.conf`` with :func:`net.listen` instead of systemd sockets (`#485 - `_). See + `_). See the following examples. .. tip:: You can find suggested network interface settings based on your @@ -93,7 +160,7 @@ ``kr_ranked_rrarray_add()``, you need to additionally call function ``kr_ranked_rrarray_finalize()`` after each batch (before changing the added memory regions). For a specific example see `changes in dns64 module - `_. + `_. .. _upgrade-from-3-to-4: @@ -176,7 +243,7 @@ * C modules defining ``*_layer`` or ``*_props`` symbols need to use a different style, but it's typically a trivial change. Instead of exporting the corresponding symbols, the module should assign pointers to its static structures inside its ``*_init()`` function. Example migration: - `bogus_log module `_. + `bogus_log module `_. .. _upgrade-from-2-to-3: diff -Nru knot-resolver-5.1.1/Dockerfile knot-resolver-5.2.1/Dockerfile --- knot-resolver-5.1.1/Dockerfile 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/Dockerfile 2020-12-09 09:44:29.000000000 +0000 @@ -1,14 +1,14 @@ # Intermediate container for Knot DNS build (not persistent) # SPDX-License-Identifier: GPL-3.0-or-later FROM debian:stable AS knot-dns-build -ARG KNOT_DNS_VERSION=v2.9.0 +ARG KNOT_DNS_VERSION=v3.0.0 # Build dependencies ENV KNOT_DNS_BUILD_DEPS git-core build-essential libtool autoconf pkg-config \ libgnutls28-dev libprotobuf-dev libprotobuf-c-dev libfstrm-dev ENV KNOT_RESOLVER_BUILD_DEPS build-essential pkg-config bsdmainutils liblmdb-dev \ libluajit-5.1-dev libuv1-dev libprotobuf-dev libprotobuf-c-dev \ - libfstrm-dev luajit lua-http libssl-dev + libfstrm-dev luajit lua-http libssl-dev libnghttp2-dev ENV BUILDENV_DEPS ${KNOT_DNS_BUILD_DEPS} ${KNOT_RESOLVER_BUILD_DEPS} RUN echo "deb http://deb.debian.org/debian stretch-backports main" > /etc/apt/sources.list.d/backports.list RUN apt-get update -qq && \ @@ -16,7 +16,7 @@ apt-get -y -qqq install -t stretch-backports meson # Install Knot DNS from sources -RUN git clone -b $KNOT_DNS_VERSION --depth=1 https://gitlab.labs.nic.cz/knot/knot-dns.git /tmp/knot-dns && \ +RUN git clone -b $KNOT_DNS_VERSION --depth=1 https://gitlab.nic.cz/knot/knot-dns.git /tmp/knot-dns && \ cd /tmp/knot-dns && \ autoreconf -if && \ ./configure --disable-static --disable-fastparser --disable-documentation \ @@ -36,7 +36,7 @@ # Install runtime dependencies ENV KNOT_DNS_RUNTIME_DEPS libgnutls30 -ENV KNOT_RESOLVER_RUNTIME_DEPS liblmdb0 luajit libluajit-5.1-2 libuv1 lua-http +ENV KNOT_RESOLVER_RUNTIME_DEPS liblmdb0 luajit libluajit-5.1-2 libuv1 lua-http libnghttp2-14 ENV KNOT_RESOLVER_RUNTIME_DEPS_HTTP lua-http lua-mmdb ENV KNOT_RESOLVER_RUNTIME_DEPS_EXTRA libfstrm0 lua-cqueues ENV KNOT_RESOLVER_RUNTIME_DEPS_SSL ca-certificates diff -Nru knot-resolver-5.1.1/etc/config/config.cluster knot-resolver-5.2.1/etc/config/config.cluster --- knot-resolver-5.1.1/etc/config/config.cluster 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/etc/config/config.cluster 2020-12-09 09:44:29.000000000 +0000 @@ -11,6 +11,8 @@ net.listen('::1', 53, { kind = 'dns'}) net.listen('127.0.0.1', 853, { kind = 'tls' }) net.listen('::1', 853, { kind = 'tls' }) +net.listen('127.0.0.1', 443, { kind = 'doh2' }) +net.listen('::1', 443, { kind = 'doh2'}) -- Refer to manual for optimal cache size cache.size = 16 * GB @@ -20,8 +22,6 @@ 'hints > iterate', -- Load /etc/hosts and allow custom root hints 'stats', -- Track internal statistics graphite = { -- Send statistics to local InfluxDB - -- `worker.id` allows us to keep per-fork statistics - prefix = hostname()..worker.id, -- Address of the Graphite/InfluxDB server host = '192.168.1.2', }, diff -Nru knot-resolver-5.1.1/etc/config/config.docker knot-resolver-5.2.1/etc/config/config.docker --- knot-resolver-5.1.1/etc/config/config.docker 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/etc/config/config.docker 2020-12-09 09:44:29.000000000 +0000 @@ -10,7 +10,7 @@ -- Listen on all interfaces (localhost would not work in Docker) net.listen('0.0.0.0', 53, { kind = 'dns' }) net.listen('0.0.0.0', 853, { kind = 'tls' }) - net.listen('0.0.0.0', 443, { kind = 'doh' }) + net.listen('0.0.0.0', 443, { kind = 'doh2' }) net.listen('0.0.0.0', 8453, { kind = 'webmgmt' }) -- Load Useful modules diff -Nru knot-resolver-5.1.1/etc/config/config.internal knot-resolver-5.2.1/etc/config/config.internal --- knot-resolver-5.1.1/etc/config/config.internal 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/etc/config/config.internal 2020-12-09 09:44:29.000000000 +0000 @@ -8,6 +8,8 @@ net.listen('::1', 53, { kind = 'dns'}) net.listen('127.0.0.1', 853, { kind = 'tls' }) net.listen('::1', 853, { kind = 'tls' }) +net.listen('127.0.0.1', 443, { kind = 'doh2' }) +net.listen('::1', 443, { kind = 'doh2' }) -- define list of internal-only domains internalDomains = policy.todnames({'company.example', 'internal.example'}) diff -Nru knot-resolver-5.1.1/etc/config/config.isp knot-resolver-5.2.1/etc/config/config.isp --- knot-resolver-5.1.1/etc/config/config.isp 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/etc/config/config.isp 2020-12-09 09:44:29.000000000 +0000 @@ -8,6 +8,8 @@ net.listen('::1', 53, { kind = 'dns'}) net.listen('127.0.0.1', 853, { kind = 'tls' }) net.listen('::1', 853, { kind = 'tls' }) +net.listen('127.0.0.1', 443, { kind = 'doh2' }) +net.listen('::1', 443, { kind = 'doh2' }) -- Refer to manual for optimal cache size cache.size = 4 * GB diff -Nru knot-resolver-5.1.1/etc/config/config.personal knot-resolver-5.2.1/etc/config/config.personal --- knot-resolver-5.1.1/etc/config/config.personal 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/etc/config/config.personal 2020-12-09 09:44:29.000000000 +0000 @@ -5,8 +5,10 @@ -- Network interface configuration net.listen('127.0.0.1', 53, { kind = 'dns' }) net.listen('127.0.0.1', 853, { kind = 'tls' }) +--net.listen('127.0.0.1', 443, { kind = 'doh2' }) net.listen('::1', 53, { kind = 'dns', freebind = true }) net.listen('::1', 853, { kind = 'tls', freebind = true }) +--net.listen('::1', 443, { kind = 'doh2' }) -- Load useful modules modules = { diff -Nru knot-resolver-5.1.1/etc/config/config.privacy knot-resolver-5.2.1/etc/config/config.privacy --- knot-resolver-5.1.1/etc/config/config.privacy 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/etc/config/config.privacy 2020-12-09 09:44:29.000000000 +0000 @@ -8,6 +8,8 @@ net.listen('::1', 53, { kind = 'dns'}) net.listen('127.0.0.1', 853, { kind = 'tls' }) net.listen('::1', 853, { kind = 'tls' }) +net.listen('127.0.0.1', 443, { kind = 'doh2' }) +net.listen('::1', 443, { kind = 'doh2' }) -- TLS server configuration -- use this to configure your TLS certificates diff -Nru knot-resolver-5.1.1/etc/config/config.splitview knot-resolver-5.2.1/etc/config/config.splitview --- knot-resolver-5.1.1/etc/config/config.splitview 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/etc/config/config.splitview 2020-12-09 09:44:29.000000000 +0000 @@ -8,14 +8,14 @@ net.listen('::1', 53, { kind = 'dns'}) net.listen('127.0.0.1', 853, { kind = 'tls' }) net.listen('::1', 853, { kind = 'tls' }) +net.listen('127.0.0.1', 443, { kind = 'doh2' }) +net.listen('::1', 443, { kind = 'doh2' }) -- Load Useful modules modules = { 'hints > iterate', -- Load /etc/hosts and allow custom root hints 'stats', -- Track internal statistics graphite = { -- Send statistics to local InfluxDB - -- `worker.id` allows us to keep per-fork statistics - prefix = hostname()..worker.id, -- Address of the Graphite/InfluxDB server host = '192.168.1.2', }, diff -Nru knot-resolver-5.1.1/etc/config/meson.build knot-resolver-5.2.1/etc/config/meson.build --- knot-resolver-5.1.1/etc/config/meson.build 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/etc/config/meson.build 2020-12-09 09:44:29.000000000 +0000 @@ -21,7 +21,7 @@ # kresd.conf install_kresd_conf = get_option('install_kresd_conf') == 'enabled' if get_option('install_kresd_conf') == 'auto' - if run_command(['test', '-r', join_paths(etc_dir, 'kresd.conf')]).returncode() == 1 + if run_command(['test', '-r', etc_dir / 'kresd.conf']).returncode() == 1 install_kresd_conf = true endif endif diff -Nru knot-resolver-5.1.1/lib/cache/api.c knot-resolver-5.2.1/lib/cache/api.c --- knot-resolver-5.1.1/lib/cache/api.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/cache/api.c 2020-12-09 09:44:29.000000000 +0000 @@ -15,6 +15,8 @@ #include #include +#include + #include "contrib/base32hex.h" #include "contrib/cleanup.h" #include "contrib/ucw/lib.h" @@ -61,7 +63,7 @@ /** Preliminary checks before stash_rrset(). Don't call if returns <= 0. */ static int stash_rrset_precond(const knot_rrset_t *rr, const struct kr_query *qry/*logs*/); -/** @internal Open cache db transaction and check internal data version. */ +/** @internal Ensure the cache version is right, possibly by clearing it. */ static int assert_right_version(struct kr_cache *cache) { /* Check cache ABI version. */ @@ -72,31 +74,34 @@ int ret = cache_op(cache, read, &key, &val, 1); if (ret == 0 && val.len == sizeof(CACHE_VERSION) && memcmp(val.data, &CACHE_VERSION, sizeof(CACHE_VERSION)) == 0) { - ret = kr_error(EEXIST); + ret = kr_ok(); } else { int oldret = ret; - /* Version doesn't match. Recreate cache and write version key. */ + /* Version doesn't match or we were unable to read it, possibly because DB is empty. + * Recreate cache and write version key. */ ret = cache_op(cache, count); - if (ret != 0) { /* Non-empty cache, purge it. */ - kr_log_info("[ ][cach] incompatible cache database detected, purging\n"); + if (ret != 0) { /* Log for non-empty cache to limit noise on fresh start. */ + kr_log_info("[cache] incompatible cache database detected, purging\n"); if (oldret) { - kr_log_verbose("bad ret: %d\n", oldret); + kr_log_verbose("[cache] reading version returned: %d\n", oldret); } else if (val.len != sizeof(CACHE_VERSION)) { - kr_log_verbose("bad length: %d\n", (int)val.len); + kr_log_verbose("[cache] version has bad length: %d\n", (int)val.len); } else { uint16_t ver; memcpy(&ver, val.data, sizeof(ver)); - kr_log_verbose("bad version: %d\n", (int)ver); + kr_log_verbose("[cache] version has bad value: %d instead of %d\n", + (int)ver, (int)CACHE_VERSION); } - ret = cache_op(cache, clear); - } - /* Either purged or empty. */ - if (ret == 0) { - /* Key/Val is invalidated by cache purge, recreate it */ - val.data = /*const-cast*/(void *)&CACHE_VERSION; - val.len = sizeof(CACHE_VERSION); - ret = cache_op(cache, write, &key, &val, 1); } + ret = cache_op(cache, clear); + } + /* Rewrite the entry even if it isn't needed. Because of cache-size-changing + * possibility it's good to always perform some write during opening of cache. */ + if (ret == 0) { + /* Key/Val is invalidated by cache purge, recreate it */ + val.data = /*const-cast*/(void *)&CACHE_VERSION; + val.len = sizeof(CACHE_VERSION); + ret = cache_op(cache, write, &key, &val, 1); } kr_cache_commit(cache); return ret; @@ -105,31 +110,52 @@ int kr_cache_open(struct kr_cache *cache, const struct kr_cdb_api *api, struct kr_cdb_opts *opts, knot_mm_t *mm) { if (!cache) { + assert(cache); return kr_error(EINVAL); } + memset(cache, 0, sizeof(*cache)); /* Open cache */ if (!api) { api = kr_cdb_lmdb(); } cache->api = api; - memset(&cache->stats, 0, sizeof(cache->stats)); int ret = cache->api->open(&cache->db, &cache->stats, opts, mm); - if (ret != 0) { - return ret; + if (ret == 0) { + ret = assert_right_version(cache); + // The included write also committed maxsize increase to the file. + } + if (ret == 0 && opts->maxsize) { + /* If some maxsize is requested and it's smaller than in-file maxsize, + * LMDB only restricts our env without changing the in-file maxsize. + * That is worked around by reopening (found no other reliable way). */ + cache->api->close(cache->db, &cache->stats); + struct kr_cdb_opts opts2; + memcpy(&opts2, opts, sizeof(opts2)); + opts2.maxsize = 0; + ret = cache->api->open(&cache->db, &cache->stats, &opts2, mm); } - cache->ttl_min = KR_CACHE_DEFAULT_TTL_MIN; - cache->ttl_max = KR_CACHE_DEFAULT_TTL_MAX; - /* Check cache ABI version */ - kr_cache_make_checkpoint(cache); - (void)assert_right_version(cache); - char *fpath; - ret = asprintf(&fpath, "%s/data.mdb", opts->path); - if (ret > 0) { + char *fpath = kr_absolutize_path(opts->path, "data.mdb"); + if (fpath) { kr_cache_emergency_file_to_remove = fpath; } else { assert(false); /* non-critical, but still */ + fpath = ""; } + + if (ret == 0 && opts->maxsize) { + size_t maxsize = cache->api->get_maxsize(cache->db); + if (maxsize > opts->maxsize) kr_log_info( + "[cache] Warning: real cache size is %zu instead of the requested %zu bytes." + " To reduce the size you need to remove the file '%s' by hand.\n", + maxsize, opts->maxsize, fpath); + } + if (ret != 0) { + return ret; + } + cache->ttl_min = KR_CACHE_DEFAULT_TTL_MIN; + cache->ttl_max = KR_CACHE_DEFAULT_TTL_MAX; + kr_cache_make_checkpoint(cache); return 0; } @@ -140,6 +166,7 @@ void kr_cache_close(struct kr_cache *cache) { + kr_cache_check_health(cache, -1); if (cache_isvalid(cache)) { cache_op(cache, close); cache->db = NULL; @@ -941,3 +968,46 @@ return ret; } +static void health_timer_cb(uv_timer_t *health_timer) +{ + struct kr_cache *cache = health_timer->data; + if (cache) + cache_op(cache, check_health); + /* We don't do anything with the return code. For example, in some situations + * the file may not exist (temporarily), and we just expect to be more lucky + * when the timer fires again. */ +} + +int kr_cache_check_health(struct kr_cache *cache, int interval) +{ + if (interval == 0) { + return cache_op(cache, check_health); + } + if (interval < 0) { + if (!cache->health_timer) + return kr_ok(); // tolerate stopping a "stopped" timer + uv_close((uv_handle_t *)cache->health_timer, (uv_close_cb)free); + cache->health_timer->data = NULL; + cache->health_timer = NULL; + return kr_ok(); + } + + assert(interval > 0); + if (!cache->health_timer) { + /* We avoid depending on daemon's symbols by using uv_default_loop. */ + cache->health_timer = malloc(sizeof(*cache->health_timer)); + if (!cache->health_timer) return kr_error(ENOMEM); + uv_loop_t *loop = uv_default_loop(); + assert(loop); + int ret = uv_timer_init(loop, cache->health_timer); + if (ret) { + free(cache->health_timer); + cache->health_timer = NULL; + return kr_error(ret); + } + cache->health_timer->data = cache; + } + assert(cache->health_timer->data); + return kr_error(uv_timer_start(cache->health_timer, health_timer_cb, interval, interval)); +} + diff -Nru knot-resolver-5.1.1/lib/cache/api.h knot-resolver-5.2.1/lib/cache/api.h --- knot-resolver-5.1.1/lib/cache/api.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/cache/api.h 2020-12-09 09:44:29.000000000 +0000 @@ -26,7 +26,7 @@ */ struct kr_cache { - knot_db_t *db; /**< Storage instance */ + kr_cdb_pt db; /**< Storage instance */ const struct kr_cdb_api *api; /**< Storage engine */ struct kr_cdb_stats stats; uint32_t ttl_min, ttl_max; /**< TTL limits */ @@ -34,6 +34,8 @@ /* A pair of stamps for detection of real-time shifts during runtime. */ struct timeval checkpoint_walltime; /**< Wall time on the last check-point. */ uint64_t checkpoint_monotime; /**< Monotonic milliseconds on the last check-point. */ + + uv_timer_t *health_timer; /**< Timer used for kr_cache_check_health() */ }; /** @@ -95,7 +97,8 @@ /** * Clear all items from the cache. * @param cache cache structure - * @return 0 or an errcode + * @return if nonzero is returned, there's a big problem - you probably want to abort(), + * perhaps except for kr_error(EAGAIN) which probably indicates transient errors. */ KR_EXPORT int kr_cache_clear(struct kr_cache *cache); @@ -181,3 +184,10 @@ */ KR_EXPORT int kr_unpack_cache_key(knot_db_val_t key, knot_dname_t *buf, uint16_t *type); + +/** Periodic kr_cdb_api::check_health(). + * @param interval in milliseconds. 0 for one-time check, -1 to stop the checks. + * @return see check_health() for one-time check; otherwise normal kr_error() code. */ +KR_EXPORT +int kr_cache_check_health(struct kr_cache *cache, int interval); + diff -Nru knot-resolver-5.1.1/lib/cache/cdb_api.h knot-resolver-5.2.1/lib/cache/cdb_api.h --- knot-resolver-5.1.1/lib/cache/cdb_api.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/cache/cdb_api.h 2020-12-09 09:44:29.000000000 +0000 @@ -11,13 +11,14 @@ /* Cache options. */ struct kr_cdb_opts { const char *path; /*!< Cache URI path. */ - size_t maxsize; /*!< Suggested cache size in bytes. */ + size_t maxsize; /*!< Suggested cache size in bytes; pass 0 to keep unchanged/default. */ }; struct kr_cdb_stats { uint64_t open; uint64_t close; uint64_t count; + uint64_t count_entries; uint64_t clear; uint64_t commit; uint64_t read; @@ -29,8 +30,15 @@ uint64_t match_miss; uint64_t read_leq; uint64_t read_leq_miss; + double usage_percent; }; +/*! Pointer to a cache structure. + * + * This struct is opaque and never defined; the purpose is to get better + * type safety than with void *. + */ +typedef struct kr_cdb *kr_cdb_pt; /*! Cache database API. * This is a simplified version of generic DB API from libknot, @@ -41,37 +49,49 @@ /* Context operations */ - int (*open)(knot_db_t **db, struct kr_cdb_stats *stat, struct kr_cdb_opts *opts, knot_mm_t *mm); - void (*close)(knot_db_t *db, struct kr_cdb_stats *stat); - int (*count)(knot_db_t *db, struct kr_cdb_stats *stat); - int (*clear)(knot_db_t *db, struct kr_cdb_stats *stat); + int (*open)(kr_cdb_pt *db, struct kr_cdb_stats *stat, struct kr_cdb_opts *opts, knot_mm_t *mm); + void (*close)(kr_cdb_pt db, struct kr_cdb_stats *stat); + int (*count)(kr_cdb_pt db, struct kr_cdb_stats *stat); + int (*clear)(kr_cdb_pt db, struct kr_cdb_stats *stat); /** Run after a row of operations to release transaction/lock if needed. */ - int (*commit)(knot_db_t *db, struct kr_cdb_stats *stat); + int (*commit)(kr_cdb_pt db, struct kr_cdb_stats *stat); /* Data access */ - int (*read)(knot_db_t *db, struct kr_cdb_stats *stat, + int (*read)(kr_cdb_pt db, struct kr_cdb_stats *stat, const knot_db_val_t *key, knot_db_val_t *val, int maxcount); - int (*write)(knot_db_t *db, struct kr_cdb_stats *stat, const knot_db_val_t *key, + int (*write)(kr_cdb_pt db, struct kr_cdb_stats *stat, const knot_db_val_t *key, knot_db_val_t *val, int maxcount); /** Remove maxcount keys. * \returns the number of succesfully removed keys or the first error code * It returns on first error, but ENOENT is not considered an error. */ - int (*remove)(knot_db_t *db, struct kr_cdb_stats *stat, + int (*remove)(kr_cdb_pt db, struct kr_cdb_stats *stat, knot_db_val_t keys[], int maxcount); /* Specialised operations */ /** Find key-value pairs that are prefixed by the given key, limited by maxcount. * \return the number of pairs or negative error. */ - int (*match)(knot_db_t *db, struct kr_cdb_stats *stat, + int (*match)(kr_cdb_pt db, struct kr_cdb_stats *stat, knot_db_val_t *key, knot_db_val_t keyval[][2], int maxcount); /** Less-or-equal search (lexicographic ordering). * On successful return, key->data and val->data point to DB-owned data. * return: 0 for equality, > 0 for less, < 0 kr_error */ - int (*read_leq)(knot_db_t *db, struct kr_cdb_stats *stat, + int (*read_leq)(kr_cdb_pt db, struct kr_cdb_stats *stat, knot_db_val_t *key, knot_db_val_t *val); + + /** Return estimated space usage (0--100). */ + double (*usage_percent)(kr_cdb_pt db); + + /** Return the current cache size limit in bytes; could be cached by check_health(). */ + size_t (*get_maxsize)(kr_cdb_pt db); + + /** Perform maintenance. + * In LMDB case it checks whether data.mdb is still the same + * and reopens it if it isn't; it errors out if the file doesn't exist anymore. + * \return 0 if OK, 1 if reopened OK, < 0 kr_error */ + int (*check_health)(kr_cdb_pt db, struct kr_cdb_stats *stat); }; diff -Nru knot-resolver-5.1.1/lib/cache/cdb_lmdb.c knot-resolver-5.2.1/lib/cache/cdb_lmdb.c --- knot-resolver-5.1.1/lib/cache/cdb_lmdb.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/cache/cdb_lmdb.c 2020-12-09 09:44:29.000000000 +0000 @@ -17,6 +17,7 @@ #include "lib/cache/cdb_lmdb.h" #include "lib/cache/cdb_api.h" #include "lib/cache/api.h" +#include "lib/cache/impl.h" #include "lib/utils.h" @@ -24,6 +25,8 @@ #define LMDB_DIR_MODE 0770 #define LMDB_FILE_MODE 0660 +/* TODO: we rely on mirrors of these two structs not changing layout + * in libknot and knot resolver! */ struct lmdb_env { size_t mapsize; @@ -41,17 +44,40 @@ MDB_txn *ro, *rw; MDB_cursor *ro_curs; } txn; + + /* Cached part of struct stat for data.mdb. */ + dev_t st_dev; + ino_t st_ino; + off_t st_size; + const char *mdb_data_path; /**< path to data.mdb, for convenience */ +}; + +struct libknot_lmdb_env { + bool shared; + unsigned dbi; + void *env; + knot_mm_t *pool; }; +/** Type-safe conversion helper. + * + * We keep lmdb_env as a separate type from kr_db_pt, as different implementation of API + * would need to define the contents differently. + */ +static inline struct lmdb_env * db2env(kr_cdb_pt db) +{ + return (struct lmdb_env *)db; +} +static inline kr_cdb_pt env2db(struct lmdb_env *env) +{ + return (kr_cdb_pt)env; +} + +static int cdb_commit(kr_cdb_pt db, struct kr_cdb_stats *stats); + /** @brief Convert LMDB error code. */ static int lmdb_error(int error) { - /* _BAD_TXN may happen with overfull DB, - * even during mdb_get with a single fork :-/ */ - if (error == MDB_BAD_TXN) { - kr_log_info("[cache] MDB_BAD_TXN, probably overfull\n"); - error = ENOSPC; - } switch (error) { case MDB_SUCCESS: return kr_ok(); @@ -77,25 +103,25 @@ return (MDB_val){ .mv_size = v.len, .mv_data = v.data }; } - -/*! \brief Set the environment map size. - * \note This also sets the maximum database size, see \fn mdb_env_set_mapsize - */ -static int set_mapsize(MDB_env *env, size_t map_size) +/** Refresh mapsize value from file, including env->mapsize. + * It's much lighter than reopen_env(). */ +static int refresh_mapsize(struct lmdb_env *env) { - long page_size = sysconf(_SC_PAGESIZE); - if (page_size <= 0) { - return KNOT_ERROR; - } + int ret = cdb_commit(env2db(env), NULL); + if (!ret) ret = lmdb_error(mdb_env_set_mapsize(env->env, 0)); + if (ret) return ret; - /* Round to page size. */ - map_size = (map_size / page_size) * page_size; - int ret = mdb_env_set_mapsize(env, map_size); - if (ret != MDB_SUCCESS) { - return lmdb_error(ret); - } + MDB_envinfo info; + ret = lmdb_error(mdb_env_info(env->env, &info)); + if (ret) return ret; - return 0; + env->mapsize = info.me_mapsize; + if (env->mapsize != env->st_size) { + kr_log_info("[cache] suspicious size of cache file '%s'" + ": file size %zu != LMDB map size %zu\n", + env->mdb_data_path, (size_t)env->st_size, env->mapsize); + } + return kr_ok(); } #define FLAG_RENEW (2*MDB_RDONLY) @@ -124,10 +150,9 @@ if (unlikely(ret == MDB_MAP_RESIZED)) { kr_log_info("[cache] detected size increased by another process\n"); - ret = mdb_env_set_mapsize(env->env, 0); - if (ret == MDB_SUCCESS) { + ret = refresh_mapsize(env); + if (ret == 0) goto retry; - } } else if (unlikely(ret == MDB_READERS_FULL)) { int cleared; ret = mdb_reader_check(env->env, &cleared); @@ -184,12 +209,12 @@ return kr_ok(); } -static int cdb_commit(knot_db_t *db, struct kr_cdb_stats *stats) +static int cdb_commit(kr_cdb_pt db, struct kr_cdb_stats *stats) { - struct lmdb_env *env = db; + struct lmdb_env *env = db2env(db); int ret = kr_ok(); if (env->txn.rw) { - stats->commit++; + if (stats) stats->commit++; ret = lmdb_error(mdb_txn_commit(env->txn.rw)); env->txn.rw = NULL; /* the transaction got freed even in case of errors */ } else if (env->txn.ro && env->txn.ro_active) { @@ -209,7 +234,7 @@ } /* Only in a read-only txn; TODO: it's a bit messy/coupled */ if (env->txn.rw) { - int ret = cdb_commit(env, stats); + int ret = cdb_commit(env2db(env), stats); if (ret) return ret; } MDB_txn *txn = NULL; @@ -221,7 +246,7 @@ } else { ret = mdb_cursor_open(txn, env->dbi, &env->txn.ro_curs); } - if (ret) return ret; + if (ret) return lmdb_error(ret); env->txn.ro_curs_active = true; success: assert(env->txn.ro_curs_active && env->txn.ro && env->txn.ro_active @@ -231,15 +256,29 @@ return kr_ok(); } -static void free_txn_ro(struct lmdb_env *env) +static void txn_free_ro(struct lmdb_env *env) { + if (env->txn.ro_curs) { + mdb_cursor_close(env->txn.ro_curs); + env->txn.ro_curs = NULL; + } if (env->txn.ro) { mdb_txn_abort(env->txn.ro); env->txn.ro = NULL; } - if (env->txn.ro_curs) { - mdb_cursor_close(env->txn.ro_curs); - env->txn.ro_curs = NULL; +} + +/** Abort all transactions. + * + * This is useful after an error happens, as those (always?) require abortion. + * It's possible that _reset() would suffice and marking cursor inactive, + * but these errors should be rare so let's close them completely. */ +static void txn_abort(struct lmdb_env *env) +{ + txn_free_ro(env); + if (env->txn.rw) { + mdb_txn_abort(env->txn.rw); + env->txn.rw = NULL; /* the transaction got freed even in case of errors */ } } @@ -249,90 +288,92 @@ assert(env && env->env); /* Get rid of any transactions. */ - cdb_commit(env, stats); - free_txn_ro(env); + txn_free_ro(env); + cdb_commit(env2db(env), stats); mdb_env_sync(env->env, 1); stats->close++; mdb_dbi_close(env->env, env->dbi); mdb_env_close(env->env); + free_const(env->mdb_data_path); memset(env, 0, sizeof(*env)); } -/*! \brief Open database environment. */ -static int cdb_open_env(struct lmdb_env *env, unsigned flags, const char *path, size_t mapsize, struct kr_cdb_stats *stats) +/** We assume that *env is zeroed and we return it zeroed on errors. */ +static int cdb_open_env(struct lmdb_env *env, const char *path, const size_t mapsize, + struct kr_cdb_stats *stats) { int ret = mkdir(path, LMDB_DIR_MODE); - if (ret == -1 && errno != EEXIST) { - return kr_error(errno); - } + if (ret && errno != EEXIST) return kr_error(errno); - MDB_env *mdb_env = NULL; stats->open++; - ret = mdb_env_create(&mdb_env); - if (ret != MDB_SUCCESS) { - return lmdb_error(ret); - } + ret = mdb_env_create(&env->env); + if (ret != MDB_SUCCESS) return lmdb_error(ret); - ret = set_mapsize(mdb_env, mapsize); - if (ret != 0) { - stats->close++; - mdb_env_close(mdb_env); - return ret; + env->mdb_data_path = kr_absolutize_path(path, "data.mdb"); + if (!env->mdb_data_path) { + ret = ENOMEM; + goto error_sys; } - ret = mdb_env_open(mdb_env, path, flags, LMDB_FILE_MODE); - if (ret != MDB_SUCCESS) { - stats->close++; - mdb_env_close(mdb_env); - return lmdb_error(ret); + /* Set map size, rounded to page size. */ + errno = 0; + const long pagesize = sysconf(_SC_PAGESIZE); + if (errno) { + ret = errno; + goto error_sys; } - /* Keep the environment pointer. */ - env->env = mdb_env; - env->mapsize = mapsize; - return 0; -} + const bool size_requested = mapsize; + if (size_requested) { + env->mapsize = (mapsize / pagesize) * pagesize; + ret = mdb_env_set_mapsize(env->env, env->mapsize); + if (ret != MDB_SUCCESS) goto error_mdb; + } -static int cdb_open(struct lmdb_env *env, const char *path, size_t mapsize, - struct kr_cdb_stats *stats) -{ /* Cache doesn't require durability, we can be * loose with the requirements as a tradeoff for speed. */ const unsigned flags = MDB_WRITEMAP | MDB_MAPASYNC | MDB_NOTLS; - int ret = cdb_open_env(env, flags, path, mapsize, stats); - if (ret != 0) { - return ret; + ret = mdb_env_open(env->env, path, flags, LMDB_FILE_MODE); + if (ret != MDB_SUCCESS) goto error_mdb; + + mdb_filehandle_t fd = -1; + ret = mdb_env_get_fd(env->env, &fd); + if (ret != MDB_SUCCESS) goto error_mdb; + + struct stat st; + if (fstat(fd, &st)) { + ret = errno; + goto error_sys; + } + env->st_dev = st.st_dev; + env->st_ino = st.st_ino; + env->st_size = st.st_size; + + /* Get the real mapsize. Shrinking can be restricted, etc. + * Unfortunately this is only reliable when not setting the size explicitly. */ + if (!size_requested) { + ret = refresh_mapsize(env); + if (ret) goto error_sys; } /* Open the database. */ MDB_txn *txn = NULL; ret = mdb_txn_begin(env->env, NULL, 0, &txn); - if (ret != MDB_SUCCESS) { - stats->close++; - mdb_env_close(env->env); - return lmdb_error(ret); - } + if (ret != MDB_SUCCESS) goto error_mdb; ret = mdb_dbi_open(txn, NULL, 0, &env->dbi); if (ret != MDB_SUCCESS) { mdb_txn_abort(txn); - stats->close++; - mdb_env_close(env->env); - return lmdb_error(ret); + goto error_mdb; } #if !defined(__MACOSX__) && !(defined(__APPLE__) && defined(__MACH__)) - auto_free char *mdb_datafile = kr_strcatdup(2, path, "/data.mdb"); - int fd = open(mdb_datafile, O_RDWR); - if (fd == -1) { - mdb_txn_abort(txn); - stats->close++; - mdb_env_close(env->env); - return kr_error(errno); + if (size_requested) { + ret = posix_fallocate(fd, 0, MAX(env->mapsize, env->st_size)); + } else { + ret = 0; } - - ret = posix_fallocate(fd, 0, mapsize); if (ret == EINVAL) { /* POSIX says this can happen when the feature isn't supported by the FS. * We haven't seen this happen on Linux+glibc but it was reported on FreeBSD.*/ @@ -340,70 +381,57 @@ "your (file)system probably doesn't support it.\n"); } else if (ret != 0) { mdb_txn_abort(txn); - stats->close++; - mdb_env_close(env->env); - close(fd); - return kr_error(ret); + goto error_sys; } - close(fd); #endif stats->commit++; ret = mdb_txn_commit(txn); - if (ret != MDB_SUCCESS) { - stats->close++; - mdb_env_close(env->env); - return lmdb_error(ret); - } + if (ret != MDB_SUCCESS) goto error_mdb; - return 0; + return kr_ok(); + +error_mdb: + ret = lmdb_error(ret); +error_sys: + free_const(env->mdb_data_path); + stats->close++; + mdb_env_close(env->env); + memset(env, 0, sizeof(*env)); + return kr_error(ret); } -static int cdb_init(knot_db_t **db, struct kr_cdb_stats *stats, +static int cdb_init(kr_cdb_pt *db, struct kr_cdb_stats *stats, struct kr_cdb_opts *opts, knot_mm_t *pool) { if (!db || !stats || !opts) { return kr_error(EINVAL); } - struct lmdb_env *env = malloc(sizeof(*env)); + /* Open the database. */ + struct lmdb_env *env = calloc(1, sizeof(*env)); if (!env) { return kr_error(ENOMEM); } - memset(env, 0, sizeof(struct lmdb_env)); - - /* Clear stale lockfiles. */ - auto_free char *lockfile = kr_strcatdup(2, opts->path, "/.cachelock"); - if (lockfile) { - if (unlink(lockfile) == 0) { - kr_log_info("[cache] cleared stale lockfile '%s'\n", lockfile); - } else if (errno != ENOENT) { - kr_log_info("[cache] failed to clear stale lockfile '%s': %s\n", lockfile, - strerror(errno)); - } - } - - /* Open the database. */ - int ret = cdb_open(env, opts->path, opts->maxsize, stats); + int ret = cdb_open_env(env, opts->path, opts->maxsize, stats); if (ret != 0) { free(env); return ret; } - *db = env; + *db = env2db(env); return 0; } -static void cdb_deinit(knot_db_t *db, struct kr_cdb_stats *stats) +static void cdb_deinit(kr_cdb_pt db, struct kr_cdb_stats *stats) { - struct lmdb_env *env = db; - cdb_close_env(env, stats); - free(env); + cdb_close_env(db2env(db), stats); + free(db); } -static int cdb_count(knot_db_t *db, struct kr_cdb_stats *stats) +static int cdb_count(kr_cdb_pt db, struct kr_cdb_stats *stats) { - struct lmdb_env *env = db; + struct lmdb_env *env = db2env(db); MDB_txn *txn = NULL; int ret = txn_get(env, &txn, true); if (ret != 0) { @@ -414,18 +442,96 @@ stats->count++; ret = mdb_stat(txn, env->dbi, &stat); - return (ret == MDB_SUCCESS) ? stat.ms_entries : lmdb_error(ret); + if (ret == MDB_SUCCESS) { + return stat.ms_entries; + } else { + txn_abort(env); + return lmdb_error(ret); + } +} + +static int reopen_env(struct lmdb_env *env, struct kr_cdb_stats *stats, const size_t mapsize) +{ + /* Keep copy as it points to current handle internals. */ + const char *path; + int ret = mdb_env_get_path(env->env, &path); + if (ret != MDB_SUCCESS) { + return lmdb_error(ret); + } + auto_free char *path_copy = strdup(path); + cdb_close_env(env, stats); + return cdb_open_env(env, path_copy, mapsize, stats); +} + +static int cdb_check_health(kr_cdb_pt db, struct kr_cdb_stats *stats) +{ + struct lmdb_env *env = db2env(db); + + struct stat st; + if (stat(env->mdb_data_path, &st)) { + int ret = errno; + return kr_error(ret); + } + + if (st.st_dev != env->st_dev || st.st_ino != env->st_ino) { + kr_log_verbose("[cache] cache file has been replaced, reopening\n"); + int ret = reopen_env(env, stats, 0); // we accept mapsize from the new file + return ret == 0 ? 1 : ret; + } + + /* Cache check through file size works OK without reopening, + * contrary to methods based on mdb_env_info(). */ + if (st.st_size == env->st_size) + return kr_ok(); + kr_log_info("[cache] detected size change (by another instance?) of file '%s'" + ": file size %zu -> file size %zu\n", + env->mdb_data_path, (size_t)env->st_size, (size_t)st.st_size); + env->st_size = st.st_size; // avoid retrying in cycle even if we fail + return refresh_mapsize(env); +} + +/** Obtain exclusive (advisory) lock by creating a file, returning FD or negative kr_error(). + * The lock is auto-released by OS in case the process finishes in any way (file remains). */ +static int lockfile_get(const char *path) +{ + assert(path); + const int fd = open(path, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); + if (fd < 0) + return kr_error(errno); + + struct flock lock_info; + memset(&lock_info, 0, sizeof(lock_info)); + lock_info.l_type = F_WRLCK; + lock_info.l_whence = SEEK_SET; + lock_info.l_start = 0; + lock_info.l_len = 1; // it's OK for locks to extend beyond the end of the file + int err; + do { + err = fcntl(fd, F_SETLK, &lock_info); + } while (err == -1 && errno == EINTR); + if (err) { + close(fd); + return kr_error(errno); + } + return fd; +} + +/** Release and remove lockfile created by lockfile_get(). Return kr_error(). */ +static int lockfile_release(int fd) +{ + assert(fd > 0); // fd == 0 is surely a mistake, in our case at least + if (close(fd)) { + return kr_error(errno); + } else { + return kr_ok(); + } } -static int cdb_clear(knot_db_t *db, struct kr_cdb_stats *stats) +static int cdb_clear(kr_cdb_pt db, struct kr_cdb_stats *stats) { - struct lmdb_env *env = db; + struct lmdb_env *env = db2env(db); stats->clear++; /* First try mdb_drop() to clear the DB; this may fail with ENOSPC. */ - /* If we didn't do this, explicit cache.clear() ran on an instance - * would lead to the instance detaching from the cache of others, - * until they reopened cache explicitly or cleared it for some reason. - */ { MDB_txn *txn = NULL; int ret = txn_get(env, &txn, false); @@ -440,75 +546,64 @@ } kr_log_info("[cache] clearing error, falling back\n"); } + /* Fallback: we'll remove the database files and reopen. + * Other instances can continue to use the removed lmdb, + * though it's best for them to reopen soon. */ /* We are about to switch to a different file, so end all txns, to be sure. */ + txn_free_ro(env); (void) cdb_commit(db, stats); - free_txn_ro(db); - /* Since there is no guarantee that there will be free - * pages to hold whole dirtied db for transaction-safe clear, - * we simply remove the database files and reopen. - * We can afford this since other readers will continue to read - * from removed file, but will reopen when encountering next - * error. */ - mdb_filehandle_t fd = -1; - int ret = mdb_env_get_fd(env->env, &fd); - if (ret != MDB_SUCCESS) { - return lmdb_error(ret); - } const char *path = NULL; - ret = mdb_env_get_path(env->env, &path); + int ret = mdb_env_get_path(env->env, &path); if (ret != MDB_SUCCESS) { return lmdb_error(ret); } - - auto_free char *mdb_datafile = kr_strcatdup(2, path, "/data.mdb"); auto_free char *mdb_lockfile = kr_strcatdup(2, path, "/lock.mdb"); - auto_free char *lockfile = kr_strcatdup(2, path, "/.cachelock"); - if (!mdb_datafile || !mdb_lockfile || !lockfile) { + auto_free char *lockfile = kr_strcatdup(2, path, "/krcachelock"); + if (!mdb_lockfile || !lockfile) { return kr_error(ENOMEM); } + /* Find if we get a lock on lockfile. */ - ret = open(lockfile, O_CREAT|O_EXCL|O_RDONLY, S_IRUSR); - if (ret == -1) { - kr_log_error("[cache] clearing failed to get ./.cachelock; retry later\n"); + const int lockfile_fd = lockfile_get(lockfile); + if (lockfile_fd < 0) { + kr_log_error("[cache] clearing failed to get ./krcachelock (%s); retry later\n", + kr_strerror(lockfile_fd)); /* As we're out of space (almost certainly - mdb_drop didn't work), * we will retry on the next failing write operation. */ - return kr_error(errno); - } - close(ret); - /* We acquired lockfile. Now find whether *.mdb are what we have open now. */ - struct stat old_stat, new_stat; - if (fstat(fd, &new_stat) || stat(mdb_datafile, &old_stat)) { - ret = errno; - unlink(lockfile); - return kr_error(ret); + return kr_error(EAGAIN); } - /* Remove underlying files only if current open environment - * points to file on the disk. Otherwise just reopen as someone - * else has already removed the files. - */ - if (old_stat.st_dev == new_stat.st_dev && old_stat.st_ino == new_stat.st_ino) { + + /* We acquired lockfile. Now find whether *.mdb are what we have open now. + * If they are not we don't want to remove them; most likely they have been + * cleaned by another instance. */ + ret = cdb_check_health(db, stats); + if (ret != 0) { + if (ret == 1) // file changed and reopened successfuly + ret = kr_ok(); + // else pass some other error + } else { kr_log_verbose("[cache] clear: identical files, unlinking\n"); // coverity[toctou] - unlink(mdb_datafile); + unlink(env->mdb_data_path); unlink(mdb_lockfile); - } else - kr_log_verbose("[cache] clear: not identical files, reopening\n"); - /* Keep copy as it points to current handle internals. */ - auto_free char *path_copy = strdup(path); - size_t mapsize = env->mapsize; - cdb_close_env(env, stats); - ret = cdb_open(env, path_copy, mapsize, stats); + ret = reopen_env(env, stats, env->mapsize); + } + /* Environment updated, release lockfile. */ - unlink(lockfile); + int lrerr = lockfile_release(lockfile_fd); + if (lrerr) { + kr_log_error("[cache] failed to release ./krcachelock: %s\n", + kr_strerror(lrerr)); + } return ret; } -static int cdb_readv(knot_db_t *db, struct kr_cdb_stats *stats, +static int cdb_readv(kr_cdb_pt db, struct kr_cdb_stats *stats, const knot_db_val_t *key, knot_db_val_t *val, int maxcount) { - struct lmdb_env *env = db; + struct lmdb_env *env = db2env(db); MDB_txn *txn = NULL; int ret = txn_get(env, &txn, true); if (ret) { @@ -522,8 +617,11 @@ stats->read++; ret = mdb_get(txn, env->dbi, &_key, &_val); if (ret != MDB_SUCCESS) { - if (ret == MDB_NOTFOUND) + if (ret == MDB_NOTFOUND) { stats->read_miss++; + } else { + txn_abort(env); + } ret = lmdb_error(ret); if (ret == kr_error(ENOSPC)) { /* we're likely to be forced to cache clear anyway */ @@ -547,18 +645,9 @@ stats->write++; int ret = mdb_put(*txn, env->dbi, &_key, &_val, flags); - /* Try to recover from doing too much writing in a single transaction. */ - if (ret == MDB_TXN_FULL) { - ret = cdb_commit(env, stats); - if (ret) { - ret = txn_get(env, txn, false); - } - if (ret) { - stats->write++; - ret = mdb_put(*txn, env->dbi, &_key, &_val, flags); - } - } + /* We don't try to recover from MDB_TXN_FULL. */ if (ret != MDB_SUCCESS) { + txn_abort(env); return lmdb_error(ret); } @@ -568,10 +657,10 @@ return kr_ok(); } -static int cdb_writev(knot_db_t *db, struct kr_cdb_stats *stats, +static int cdb_writev(kr_cdb_pt db, struct kr_cdb_stats *stats, const knot_db_val_t *key, knot_db_val_t *val, int maxcount) { - struct lmdb_env *env = db; + struct lmdb_env *env = db2env(db); MDB_txn *txn = NULL; int ret = txn_get(env, &txn, false); @@ -591,10 +680,10 @@ return ret; } -static int cdb_remove(knot_db_t *db, struct kr_cdb_stats *stats, +static int cdb_remove(kr_cdb_pt db, struct kr_cdb_stats *stats, knot_db_val_t keys[], int maxcount) { - struct lmdb_env *env = db; + struct lmdb_env *env = db2env(db); MDB_txn *txn = NULL; int ret = txn_get(env, &txn, false); int deleted = 0; @@ -609,16 +698,19 @@ else if (ret == KNOT_ENOENT) { stats->remove_miss++; ret = kr_ok(); /* skip over non-existing entries */ + } else { + txn_abort(env); + break; } } return ret < 0 ? ret : deleted; } -static int cdb_match(knot_db_t *db, struct kr_cdb_stats *stats, +static int cdb_match(kr_cdb_pt db, struct kr_cdb_stats *stats, knot_db_val_t *key, knot_db_val_t keyval[][2], int maxcount) { - struct lmdb_env *env = db; + struct lmdb_env *env = db2env(db); MDB_txn *txn = NULL; int ret = txn_get(env, &txn, true); if (ret != 0) { @@ -629,6 +721,7 @@ MDB_cursor *cur = NULL; ret = mdb_cursor_open(txn, env->dbi, &cur); if (ret != 0) { + txn_abort(env); return lmdb_error(ret); } @@ -638,6 +731,9 @@ ret = mdb_cursor_get(cur, &cur_key, &cur_val, MDB_SET_RANGE); if (ret != MDB_SUCCESS) { mdb_cursor_close(cur); + if (ret != MDB_NOTFOUND) { + txn_abort(env); + } return lmdb_error(ret); } @@ -658,19 +754,23 @@ stats->match++; ret = mdb_cursor_get(cur, &cur_key, &cur_val, MDB_NEXT); } - if (results == 0) - stats->match_miss++; - mdb_cursor_close(cur); + if (ret != MDB_SUCCESS && ret != MDB_NOTFOUND) { + txn_abort(env); + return lmdb_error(ret); + } else if (results == 0) { + stats->match_miss++; + } return results; } -static int cdb_read_leq(knot_db_t *env, struct kr_cdb_stats *stats, +static int cdb_read_leq(kr_cdb_pt db, struct kr_cdb_stats *stats, knot_db_val_t *key, knot_db_val_t *val) { - assert(env && key && key->data && val); + assert(db && key && key->data && val); + struct lmdb_env *env = db2env(db); MDB_cursor *curs = NULL; int ret = txn_curs_get(env, &curs, stats); if (ret) return ret; @@ -679,10 +779,7 @@ MDB_val val2_m = { 0, NULL }; stats->read_leq++; ret = mdb_cursor_get(curs, &key2_m, &val2_m, MDB_SET_RANGE); - if (ret) { - stats->read_leq_miss++; - return lmdb_error(ret); - } + if (ret) goto failure; /* test for equality //:unlikely */ if (key2_m.mv_size == key->len && memcmp(key2_m.mv_data, key->data, key->len) == 0) { @@ -694,18 +791,51 @@ /* we must be greater than key; do one step to smaller */ stats->read_leq++; ret = mdb_cursor_get(curs, &key2_m, &val2_m, MDB_PREV); - if (ret) { - stats->read_leq_miss++; - return lmdb_error(ret); - } + if (ret) goto failure; ret = 1; success: /* finalize the output */ *key = val_mdb2knot(key2_m); *val = val_mdb2knot(val2_m); return ret; +failure: + if (ret == MDB_NOTFOUND) { + stats->read_leq_miss++; + } else { + txn_abort(env); + } + return lmdb_error(ret); +} + +static double cdb_usage_percent(kr_cdb_pt db) +{ + knot_db_t *kdb = kr_cdb_pt2knot_db_t(db); + const size_t db_size = knot_db_lmdb_get_mapsize(kdb); + const size_t db_usage_abs = knot_db_lmdb_get_usage(kdb); + const double db_usage = (double)db_usage_abs / db_size * 100.0; + free(kdb); + return db_usage; } +static size_t cdb_get_maxsize(kr_cdb_pt db) +{ + return db2env(db)->mapsize; +} + +/** Conversion between knot and lmdb structs. */ +knot_db_t *kr_cdb_pt2knot_db_t(kr_cdb_pt db) +{ + /* this is struct lmdb_env as in resolver/cdb_lmdb.c */ + const struct lmdb_env *kres_db = db2env(db); + struct libknot_lmdb_env *libknot_db = malloc(sizeof(*libknot_db)); + if (libknot_db != NULL) { + libknot_db->shared = false; + libknot_db->pool = NULL; + libknot_db->env = kres_db->env; + libknot_db->dbi = kres_db->dbi; + } + return libknot_db; +} const struct kr_cdb_api *kr_cdb_lmdb(void) { @@ -714,7 +844,10 @@ cdb_init, cdb_deinit, cdb_count, cdb_clear, cdb_commit, cdb_readv, cdb_writev, cdb_remove, cdb_match, - cdb_read_leq + cdb_read_leq, + cdb_usage_percent, + cdb_get_maxsize, + cdb_check_health, }; return &api; diff -Nru knot-resolver-5.1.1/lib/cache/cdb_lmdb.h knot-resolver-5.2.1/lib/cache/cdb_lmdb.h --- knot-resolver-5.1.1/lib/cache/cdb_lmdb.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/cache/cdb_lmdb.h 2020-12-09 09:44:29.000000000 +0000 @@ -9,3 +9,8 @@ KR_EXPORT KR_CONST const struct kr_cdb_api *kr_cdb_lmdb(void); + +/** Create a pointer for knot_db_lmdb_api. You free() it to release it. */ +KR_EXPORT +knot_db_t *kr_cdb_pt2knot_db_t(kr_cdb_pt db); + diff -Nru knot-resolver-5.1.1/lib/cache/entry_list.c knot-resolver-5.2.1/lib/cache/entry_list.c --- knot-resolver-5.1.1/lib/cache/entry_list.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/cache/entry_list.c 2020-12-09 09:44:29.000000000 +0000 @@ -168,21 +168,25 @@ { int ret = cache_op(cache, write, key, val, 1); if (!ret) return kr_ok(); - /* Clear cache if overfull. It's nontrivial to do better with LMDB. - * LATER: some garbage-collection mechanism. */ - if (ret == kr_error(ENOSPC)) { - ret = kr_cache_clear(cache); - const char *msg = "[cache] clearing because overfull, ret = %d\n"; - if (ret) { - kr_log_error(msg, ret); - } else { - kr_log_info(msg, ret); - ret = kr_error(ENOSPC); - } - return ret; + + if (ret != kr_error(ENOSPC)) { /* failing a write isn't too bad */ + VERBOSE_MSG(qry, "=> failed backend write, ret = %d\n", ret); + return kr_error(ret); + } + + /* Cache is overfull. Using kres-cache-gc service should prevent this. + * As a fallback, try clearing it. */ + ret = kr_cache_clear(cache); + switch (ret) { + default: + kr_log_error("CRITICAL: clearing cache failed: %s; fatal error, aborting\n", + kr_strerror(ret)); + abort(); + case 0: + kr_log_info("[cache] overfull cache cleared\n"); + case -EAGAIN: // fall-through; krcachelock race -> retry later + return kr_error(ENOSPC); } - VERBOSE_MSG(qry, "=> failed backend write, ret = %d\n", ret); - return kr_error(ret ? ret : ENOSPC); } diff -Nru knot-resolver-5.1.1/lib/cache/overflow.test.integr/deckard.yaml knot-resolver-5.2.1/lib/cache/overflow.test.integr/deckard.yaml --- knot-resolver-5.1.1/lib/cache/overflow.test.integr/deckard.yaml 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/lib/cache/overflow.test.integr/deckard.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +programs: +- name: kresd1 + binary: kresd + additional: + - -n + templates: + - lib/cache/overflow.test.integr/kresd_config.j2 + - tests/config/test_dns_generators.lua + configs: + - config + - dns_gen.lua +- name: kresd2 + binary: kresd + additional: + - -n + templates: + - lib/cache/overflow.test.integr/kresd_config.j2 + - tests/config/test_dns_generators.lua + configs: + - config + - dns_gen.lua diff -Nru knot-resolver-5.1.1/lib/cache/overflow.test.integr/kresd_config.j2 knot-resolver-5.2.1/lib/cache/overflow.test.integr/kresd_config.j2 --- knot-resolver-5.1.1/lib/cache/overflow.test.integr/kresd_config.j2 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/lib/cache/overflow.test.integr/kresd_config.j2 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,76 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +trust_anchors.remove('.') +{% for TAF in TRUST_ANCHOR_FILES %} +trust_anchors.add_file('{{TAF}}') +{% endfor %} + +{% raw %} +-- Disable RFC5011 TA update +if ta_update then + modules.unload('ta_update') +end + +-- Disable RFC8145 signaling, scenario doesn't provide expected answers +if ta_signal_query then + modules.unload('ta_signal_query') +end + +-- Disable RFC8109 priming, scenario doesn't provide expected answers +if priming then + modules.unload('priming') +end + +-- Disable this module because it make one priming query +if detect_time_skew then + modules.unload('detect_time_skew') +end + +verbose(true) +policy.add(policy.all(policy.DEBUG_ALWAYS)) + +cache.open(1*MB) + +{% endraw %} + +-- both instances listen on both addresses +-- so queries get distributed between them randomly +net.listen('{{programs[0]["address"]}}') +net.listen('{{programs[1]["address"]}}') + +{% raw %} +-- Self-checks on globals +assert(help() ~= nil) +assert(worker.id ~= nil) +-- Self-checks on facilities +assert(cache.stats() ~= nil) +assert(cache.backends() ~= nil) +assert(worker.stats() ~= nil) +assert(net.interfaces() ~= nil) +-- Self-checks on loaded stuff +{% endraw %} + +assert(net.list()[1].transport.ip == '{{programs[0]["address"]}}') + +{% raw %} +assert(#modules.list() > 0) +-- Self-check timers +ev = event.recurrent(1 * sec, function (ev) return 1 end) +event.cancel(ev) +ev = event.after(0, function (ev) return 1 end) + +local ffi = require('ffi') +local kr_cach = kres.context().cache + +-- canary for cache overflow +local kr_rrset = kres.rrset( + todname('www.example.com'), + kres.type.A, + kres.class.IN, + 604800) +assert(kr_rrset:add_rdata('\192\000\002\001', 4)) +assert(kr_cach:insert(kr_rrset, nil, ffi.C.KR_RANK_SECURE)) + +local generators = dofile('./dns_gen.lua') +event.after(0, generators.gen_batch) +{% endraw %} diff -Nru knot-resolver-5.1.1/lib/cache/overflow.test.integr/world_cz_lidovky_www.rpl knot-resolver-5.2.1/lib/cache/overflow.test.integr/world_cz_lidovky_www.rpl --- knot-resolver-5.1.1/lib/cache/overflow.test.integr/world_cz_lidovky_www.rpl 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/lib/cache/overflow.test.integr/world_cz_lidovky_www.rpl 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,1114 @@ +# -- SPDX-License-Identifier: GPL-3.0-or-later +stub-addr: 2001:dc3::35 +val-override-date: "20170228130000" +trust-anchor: ". 172800 IN DS 19036 8 2 49aac11d7b6f6446702e54a1607371607a1a41855200fd2ce1cdde32f24e8fb5" +CONFIG_END + +SCENARIO_BEGIN Real-world DNS tree with repeated queries. Intended use is to test background tasks specified in Lua config. + +;root +RANGE_BEGIN 0 10000 + ADDRESS 2001:dc3::35 + ADDRESS 198.41.0.4 + ADDRESS 192.228.79.201 + ADDRESS 192.33.4.12 + ADDRESS 199.7.91.13 + ADDRESS 192.203.230.10 + ADDRESS 192.5.5.241 + ADDRESS 192.112.36.4 + ADDRESS 198.97.190.53 + ADDRESS 192.36.148.17 + ADDRESS 192.58.128.30 + ADDRESS 193.0.14.129 + ADDRESS 199.7.83.42 + ADDRESS 202.12.27.33 + ADDRESS 2001:503:ba3e::2:30 + ADDRESS 2001:500:84::b + ADDRESS 2001:500:2::c + ADDRESS 2001:500:2d::d + ADDRESS 2001:500:a8::e + ADDRESS 2001:500:2f::f + ADDRESS 2001:500:12::d0d + ADDRESS 2001:500:1::53 + ADDRESS 2001:7fe::53 + ADDRESS 2001:503:c27::2:30 + ADDRESS 2001:7fd::1 + ADDRESS 2001:500:9f::42 + ADDRESS 2001:dc3::35 + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR AA RD DO NOERROR + SECTION QUESTION + . IN DS + SECTION AUTHORITY + . 86400 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2017022701 1800 900 604800 86400 + . 86400 IN RRSIG SOA 8 0 86400 20170312170000 20170227160000 61045 . GhyRFKg8xu/asiFmIMifBOFUeJlL++ncqDoBLbYoviben3WNrdU7vJxZ Cm3EZ8HEYr2gFFkupaHBZt+P6GdX9lU8aw7yOZ8ZXV48S209Jo3PkHxH iVOtaC7QzkJPiZUgh06MuWgQoeNJSVqGTCy+TlTlMLqGndNcpT0rkX7H 0gCcuaZcBv0nqEPKqZeq8XFVIfiaUCKz/kkkO0vgP9euN+WT+68hng4F oIQ0eAPIUL6XBW2uWubWS2Yd8C+g/++qeLnte7QYF+9By5HuN6fXskba 0uph3gzjWArn+SYQhEWyqbS6wb0LloAawt9LW7neJYOMFhlU1AOScGjn e8rfBw== + . 86400 IN NSEC aaa. NS SOA RRSIG NSEC DNSKEY + . 86400 IN RRSIG NSEC 8 0 86400 20170312170000 20170227160000 61045 . MLiCUaeASll0V1x0imORnQodzd/6LuDpa8XfebmNE7eGMda62HCK9kB2 I5Yvcc6naw1nzJVSVNIjDQyAKHgSWy457vwvWbEdCuD5XS8A1/drP13x pfP91XG3qPswx3u1i4cLSTO5VJi1lup1Qr1UrN54kNbRp2sS65VKXOH4 4I6bwA1CBOmU6EHlyI2nymZDqCRaTdWjyoYSZ1zkucSjEgn8GtyniNiS p7AfNLnnJ6poKSCcOj2hSQTb58i7B7TJt/JQWb6ko12rcSEVxZljhqHc XzR+i8Bgfpj9ha83tcZwDFQQy4mKjSkboOEoRe8Z5qKIb5DF0wn0vB+M LClQJg== + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR AA RD DO NOERROR + SECTION QUESTION + . IN NS + SECTION ANSWER + . 518400 IN NS m.root-servers.net. + . 518400 IN NS k.root-servers.net. + . 518400 IN NS a.root-servers.net. + . 518400 IN NS l.root-servers.net. + . 518400 IN NS j.root-servers.net. + . 518400 IN NS i.root-servers.net. + . 518400 IN NS e.root-servers.net. + . 518400 IN NS b.root-servers.net. + . 518400 IN NS h.root-servers.net. + . 518400 IN NS d.root-servers.net. + . 518400 IN NS c.root-servers.net. + . 518400 IN NS f.root-servers.net. + . 518400 IN NS g.root-servers.net. + . 518400 IN RRSIG NS 8 0 518400 20170312170000 20170227160000 61045 . iqk4z3W6lGfSgvbPGl4JPVDca+21mXayctqY0FO1a9YhCSxLQGsV/0eK IfYOGHMCBr2szIactoznQgFybjNG/I5bKo+EU4U0tNNVwrUHWTMsAraQ yIS/efPZyKAHSzKZjlcRVOFbFPA/DWp6JzMhfXaBYMLcsA8ZT/CwCnxF a7wInMupWskMwXXhTgGci+PJVKm+TK5hEtYYnb3Ny2lxoWtTPJuZufM9 1xg2YXs6njo1gKzj3zaTwpndeBbYN78ZfETmPsjyr7X144v9qe7qygCO dTjy+cly1JG1prI9yHaU5zJk3X9VcvWWRR3ACQOFfzthFqyEoHjQmEBe XQHCRg== + SECTION ADDITIONAL + a.root-servers.net. 3600000 IN A 198.41.0.4 + b.root-servers.net. 3600000 IN A 192.228.79.201 + c.root-servers.net. 3600000 IN A 192.33.4.12 + d.root-servers.net. 3600000 IN A 199.7.91.13 + e.root-servers.net. 3600000 IN A 192.203.230.10 + f.root-servers.net. 3600000 IN A 192.5.5.241 + g.root-servers.net. 3600000 IN A 192.112.36.4 + h.root-servers.net. 3600000 IN A 198.97.190.53 + i.root-servers.net. 3600000 IN A 192.36.148.17 + j.root-servers.net. 3600000 IN A 192.58.128.30 + k.root-servers.net. 3600000 IN A 193.0.14.129 + l.root-servers.net. 3600000 IN A 199.7.83.42 + m.root-servers.net. 3600000 IN A 202.12.27.33 + a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 + b.root-servers.net. 3600000 IN AAAA 2001:500:84::b + c.root-servers.net. 3600000 IN AAAA 2001:500:2::c + d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d + e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e + f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f + g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d + h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 + i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 + j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 + k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 + l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 + m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR AA RD DO NOERROR + SECTION QUESTION + . IN DNSKEY + SECTION ANSWER + . 172800 IN DNSKEY 256 3 8 AwEAAYvgWbYkpeGgdPKaKTJU3Us4YSTRgy7+dzvfArIhi2tKoZ/WR1Df w883SOU6Uw7tpVRkLarN0oIMK/xbOBD1DcXnyfElBwKsz4sVVWmfyr/x +igD/UjrcJ5zEBUrUmVtHyjar7ccaVc1/3ntkhZjI1hcungAlOhPhHlk MeX+5Azx6GdX//An5OgrdyH3o/JmOPMDX1mt806JI/hf0EwAp1pBwo5e 8SrSuR1tD3sgNjr6IzCdrKSgqi92z49zcdis3EaY199WFW60DCS7ydu+ +T5Xa+GyOw1quagwf/JUC/mEpeBQYWrnpkBbpDB3sy4+P2i8iCvavehb RyVm9U0MlIc= + . 172800 IN DNSKEY 257 3 8 AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjF FVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoX bfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaD X6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpz W5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relS Qageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulq QxA+Uk1ihz0= + . 172800 IN RRSIG DNSKEY 8 0 172800 20170313000000 20170220000000 19036 . Dgzxpg2Lr39HXuHwuJWYCGySxsm92RY8TRuSOstPVcHc7we0d4pW7Znt 33j9fzrxdvoVFAvqSioilVKiOY49M8N+sXcsfTK3cnh7ijTA7suXd4ht TClLN7Dn+ZAjhoyjLm5hf7P/jL0K9KKcOqEqS+uqX3W2WeCvUwT3BY6A t2r+pKSVnoX0uFWJX+mmCh4veYW3eoBzAqwAVbCE5hl2tVbf/vzpa8eW kHegVmm5smKzK2ciYOqExl3FtLgf6dp+HTpruS2oN1JPxm4f1IZhVwT0 pSEu8OUNOV8WSbLn3P9aUpq894Tf1i0/AEtFtx2tRCdw3lSKOugfneo0 PYo1JQ== + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO NOERROR + SECTION QUESTION + cz. IN DS + SECTION ANSWER + cz. 86400 IN DS 54576 10 2 397E50C85EDE9CDE33F363A9E66FD1B216D788F8DD438A57A423A386 869C8F06 + cz. 86400 IN RRSIG DS 8 1 86400 20170312170000 20170227160000 61045 . irp/lUXakeZMwVjkZQOOt6xAB2Fcglo7nxmUkHBFjsB5lp61Pg6eyt8u xvGrTdv4mv6PH5q0c7bfKo0Ngtedbq8gZ6VHXfcKUU7vP5BUmePWPyvf khKcafAO7D2wIw9gKxPB0syd3woUP7PlQ1Rg/rUMwDnEXtS7zEqzrVbb VkjdqvdgLUsInAc9zdP72qRp9cJhuoRm0nco1uo2ZLUC04poGxSNzXTw hKhngqHDTqD1nr/Wnq7uXtmLyvFelICSpSHmkrCxnou7EtPybC+W+fna f8o7FebZBnB71t5d8s2kxlb+KrWXUMv8VOdZdZTQTN8M5LeKSBL7RnXM 1FbCiQ== + ENTRY_END + + ENTRY_BEGIN + MATCH opcode subdomain + ADJUST copy_id copy_query + REPLY QR RD DO NOERROR + SECTION QUESTION + CZ. IN NS + SECTION AUTHORITY + cz. 172800 IN NS b.ns.nic.cz. + cz. 172800 IN NS a.ns.nic.cz. + cz. 172800 IN NS c.ns.nic.cz. + cz. 172800 IN NS d.ns.nic.cz. + cz. 86400 IN DS 54576 10 2 397E50C85EDE9CDE33F363A9E66FD1B216D788F8DD438A57A423A386 869C8F06 + cz. 86400 IN RRSIG DS 8 1 86400 20170312170000 20170227160000 61045 . irp/lUXakeZMwVjkZQOOt6xAB2Fcglo7nxmUkHBFjsB5lp61Pg6eyt8u xvGrTdv4mv6PH5q0c7bfKo0Ngtedbq8gZ6VHXfcKUU7vP5BUmePWPyvf khKcafAO7D2wIw9gKxPB0syd3woUP7PlQ1Rg/rUMwDnEXtS7zEqzrVbb VkjdqvdgLUsInAc9zdP72qRp9cJhuoRm0nco1uo2ZLUC04poGxSNzXTw hKhngqHDTqD1nr/Wnq7uXtmLyvFelICSpSHmkrCxnou7EtPybC+W+fna f8o7FebZBnB71t5d8s2kxlb+KrWXUMv8VOdZdZTQTN8M5LeKSBL7RnXM 1FbCiQ== + SECTION ADDITIONAL + a.ns.nic.cz. 155678 IN A 194.0.12.1 + b.ns.nic.cz. 155678 IN A 194.0.13.1 + c.ns.nic.cz. 153044 IN A 194.0.14.1 + d.ns.nic.cz. 153044 IN A 193.29.206.1 + a.ns.nic.cz. 153051 IN AAAA 2001:678:f::1 + b.ns.nic.cz. 153051 IN AAAA 2001:678:10::1 + c.ns.nic.cz. 155678 IN AAAA 2001:678:11::1 + d.ns.nic.cz. 155678 IN AAAA 2001:678:1::1 + ENTRY_END + + + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO NOERROR + SECTION QUESTION + net. IN DS + SECTION ANSWER + net. 86400 IN DS 35886 8 2 7862B27F5F516EBE19680444D4CE5E762981931842C465F00236401D 8BD973EE + net. 86400 IN RRSIG DS 8 1 86400 20170312170000 20170227160000 61045 . bRSoCpmN/6LhmSB7i68N0zO08WwVikjm6HhOyZMhyjF4sfAaDoeTMs5E XjflCZMly1SP8CwkK6Wz6Ozb8nMUHEsYOhASVBkYC/ImBpqIV5LxaCbW 4L7g5Mwam0MBZb4hybI7JUyuiRONVy3YYk+eUvyf4/flu3Cl14a36LYv 2In/ECg9sV8cMOrYs722vigvzH5eHLIZTOhGBE2//uH8pw1YnMW9sYRj f5algDGge4hZvi0ieQyzfT3UqmQEmZZCz+vdlPtgKqIj6+I+V+SZOB2d aBkb/0NrWIx+iE+fqP6jx7I2HCobVnYUvJjL/t6O1shC4mxcDghLLUpf fSnEag== + ENTRY_END + + ENTRY_BEGIN + MATCH opcode subdomain + ADJUST copy_id copy_query + REPLY QR RD DO NOERROR + SECTION QUESTION + net. IN NS + SECTION AUTHORITY + net. 172800 IN NS j.gtld-servers.net. + net. 172800 IN NS e.gtld-servers.net. + net. 172800 IN NS c.gtld-servers.net. + net. 172800 IN NS m.gtld-servers.net. + net. 172800 IN NS d.gtld-servers.net. + net. 172800 IN NS i.gtld-servers.net. + net. 172800 IN NS a.gtld-servers.net. + net. 172800 IN NS g.gtld-servers.net. + net. 172800 IN NS h.gtld-servers.net. + net. 172800 IN NS b.gtld-servers.net. + net. 172800 IN NS k.gtld-servers.net. + net. 172800 IN NS f.gtld-servers.net. + net. 172800 IN NS l.gtld-servers.net. + net. 86400 IN DS 35886 8 2 7862B27F5F516EBE19680444D4CE5E762981931842C465F00236401D 8BD973EE + net. 86400 IN RRSIG DS 8 1 86400 20170312170000 20170227160000 61045 . bRSoCpmN/6LhmSB7i68N0zO08WwVikjm6HhOyZMhyjF4sfAaDoeTMs5E XjflCZMly1SP8CwkK6Wz6Ozb8nMUHEsYOhASVBkYC/ImBpqIV5LxaCbW 4L7g5Mwam0MBZb4hybI7JUyuiRONVy3YYk+eUvyf4/flu3Cl14a36LYv 2In/ECg9sV8cMOrYs722vigvzH5eHLIZTOhGBE2//uH8pw1YnMW9sYRj f5algDGge4hZvi0ieQyzfT3UqmQEmZZCz+vdlPtgKqIj6+I+V+SZOB2d aBkb/0NrWIx+iE+fqP6jx7I2HCobVnYUvJjL/t6O1shC4mxcDghLLUpf fSnEag== + SECTION ADDITIONAL + a.gtld-servers.net. 172800 IN A 192.5.6.30 + b.gtld-servers.net. 172800 IN A 192.33.14.30 + c.gtld-servers.net. 172800 IN A 192.26.92.30 + d.gtld-servers.net. 172800 IN A 192.31.80.30 + e.gtld-servers.net. 172800 IN A 192.12.94.30 + f.gtld-servers.net. 172800 IN A 192.35.51.30 + g.gtld-servers.net. 172800 IN A 192.42.93.30 + h.gtld-servers.net. 172800 IN A 192.54.112.30 + i.gtld-servers.net. 172800 IN A 192.43.172.30 + j.gtld-servers.net. 172800 IN A 192.48.79.30 + k.gtld-servers.net. 172800 IN A 192.52.178.30 + l.gtld-servers.net. 172800 IN A 192.41.162.30 + m.gtld-servers.net. 172800 IN A 192.55.83.30 + a.gtld-servers.net. 172800 IN AAAA 2001:503:a83e::2:30 + b.gtld-servers.net. 172800 IN AAAA 2001:503:231d::2:30 + ENTRY_END + + +RANGE_END + +;cz +RANGE_BEGIN 0 10000 + ADDRESS 194.0.12.1 + ADDRESS 194.0.13.1 + ADDRESS 194.0.14.1 + ADDRESS 193.29.206.1 + ADDRESS 2001:678:f::1 + ADDRESS 2001:678:10::1 + ADDRESS 2001:678:11::1 + ADDRESS 2001:678:1::1 + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO NOERROR + SECTION QUESTION + nic.cz. IN DS + SECTION ANSWER + nic.cz. 14400 IN DS 61281 13 2 4104D40C8FE2030BF7A09A199FCF37B36F7EC8DDD16F5A84F2E61C24 8D3AFD0F + nic.cz. 14400 IN RRSIG DS 10 2 14400 20170312221837 20170228130956 58211 cz. LKiLo/EqBTsv1e6s8p5UfN/qZfd3Dnf5XGO11vW2pELybdmmpD5clR/v mz+cc4zxLiQAxDnBpdUPAPdxcPlILa5mjMfJy2ExsQOZhcbIUInRala6 GhBfGy3bnniJkJCu7sAIsf+HyDM92pFSql67ErS0ROERBhSRVbfunEBy FCo= + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO AA NOERROR + SECTION QUESTION + a.ns.nic.cz. IN A + SECTION ANSWER + a.ns.nic.cz. 1800 IN A 194.0.12.1 + a.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20170314061428 20170228072511 16836 nic.cz. rJsAWa5cYGooRzu5+jRW5m4ebYHPkHRBwrLT5P7lIkT5VkcoIRYMcdYf gr+pXJFM9IduSZJXfomumKyOYHts7Q== + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO AA NOERROR + SECTION QUESTION + b.ns.nic.cz. IN A + SECTION ANSWER + b.ns.nic.cz. 1800 IN A 194.0.13.1 + b.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20170314044412 20170228072511 16836 nic.cz. 6dOVqiXZgfp1fltylhOAYvfILWCGu61cpabseUNTmb20TZR1GuI5ueTS lmYa93o46M+01ATfrkwBWZC065G8yg== + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO AA NOERROR + SECTION QUESTION + c.ns.nic.cz. IN A + SECTION ANSWER + c.ns.nic.cz. 1800 IN A 194.0.14.1 + c.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20170314015427 20170228072511 16836 nic.cz. 824yJyP2dWJ7phi63r1/24v0SbzU9FVi7b8IkXIrQ+3aCTyXKugE8l8C qLz6qwulzu2aG+8SyfvenXDSySqiqQ== + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO AA NOERROR + SECTION QUESTION + d.ns.nic.cz. IN A + SECTION ANSWER + d.ns.nic.cz. 1800 IN A 193.29.206.1 + d.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20170313233915 20170228072511 16836 nic.cz. KAlDHStrGzdtoBe9epn87lsggg6vVvHPGMPv/njWSTns7BX0//fTxfOc iOXdutsQhq/8Z2o87pKzE2F9FbE6Hw== + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO AA NOERROR + SECTION QUESTION + a.ns.nic.cz. IN AAAA + SECTION ANSWER + a.ns.nic.cz. 1800 IN AAAA 2001:678:f::1 + a.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20170313215345 20170228072511 16836 nic.cz. GMmWVeCiIzq2kt4VmsDXGSaAWMtDB78+Yz7qgEqu5C1PAUUBQo4o5lU/ igGhIJHk2BSljJxjaL+LlnW3uOeCDQ== + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO AA NOERROR + SECTION QUESTION + b.ns.nic.cz. IN AAAA + SECTION ANSWER + b.ns.nic.cz. 1800 IN AAAA 2001:678:10::1 + b.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20170314011606 20170228072511 16836 nic.cz. ALfV0l2a4D1CITaZdP5k5Mc+uTZ1dSb3SRm1Z+AQmeQLKI7YrFlOCuUa q90yMQnG+0GMS4uwSmIcT3V2cjpBXw== + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO AA NOERROR + SECTION QUESTION + c.ns.nic.cz. IN AAAA + SECTION ANSWER + c.ns.nic.cz. 1800 IN AAAA 2001:678:11::1 + c.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20170313184936 20170228072511 16836 nic.cz. U/tpYchWTle9loCW8fPIMoF3zto86UmFFCSnU7sFG9Qxk4I8fNUro1nT fAeJlrI7L7Yx9qlJTAllzrPjuw+3IA== + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO AA NOERROR + SECTION QUESTION + d.ns.nic.cz. IN AAAA + SECTION ANSWER + d.ns.nic.cz. 1800 IN AAAA 2001:678:1::1 + d.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20170313124110 20170228072511 16836 nic.cz. kOI6MVJDSexQQ6uGT7KBjrTB2PDs49Cm65heInzMGZ20R75wO0JhSlce /T+Rpw3R0XpBre39h2DF7yBgePr+qg== + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO AA NOERROR + SECTION QUESTION + cz. IN NS + SECTION ANSWER + cz. 14400 IN NS a.ns.nic.cz. + cz. 14400 IN NS b.ns.nic.cz. + cz. 14400 IN NS d.ns.nic.cz. + cz. 14400 IN NS c.ns.nic.cz. + cz. 14400 IN RRSIG NS 10 1 14400 20170307183707 20170222123920 58211 cz. Ma2XNvMziL3GtyLXtKcCBBG12+r7Uor3OFTw6c7Txk573/Y33IMnbN6B iKz0hZw0XK5c6nHciMEDkH2K772fcskHjEnOg+bJMBJlUmqskbVBmwpZ Dd156QC9OIfcE6yJYa6Y1jOegpgCaZLXRDOZodtvvTkYWNP/D01cmsF6 U+4= + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id copy_query + REPLY QR AA RD DO NOERROR + SECTION QUESTION + CZ. IN DNSKEY + SECTION ANSWER + cz. 18000 IN DNSKEY 256 3 10 AwEAAdWL2Br92Vx0dLEOOB8y02ss8LtKIyGlLJ2ymJ02WqR3AAEEZN0f NPKF77kdKsjlG8DlzmSIOR12aa9EhpXqyHOwWI0kHOMJVnn6ZKFIAl71 JP/dYIcshYUxKZZMe+zEAUrVtzlLVDtM6cDOPDuBNa1ujYec3eJl9Ipq eUEG6gAH + cz. 18000 IN DNSKEY 257 3 10 AwEAAay0hi4HN2r/BqMQTpIPIVDyjmyF+9ZWvr5Lewx+q+947o/GrRv4 FGFfkZxf9CFfYVUf0jG5Yq4i06pGVNwJl81HS9Ux2oeHRXUvgtLnl5He RVLL+zgI5byx9HSNr4bPO8ZEn5OjoayhkNyGSFr4VWrzQk/K02vLP4d1 cCEzUQy30eyZto2/tG5ZwCU/iRkS1PJOcOW98hiFIfFDZv1XjbEpqEYh T2PATs6rt+BKwSHKGISmg1PNdg+y0rItemYMWr1f9BGAdtTWoPCPCYPj OZMPoIyA4tMscD+ww54Jf/QNoHccY4hO1yHiuAXG7SUn8jo0IKQ9W7JJ xES0aqFCX/0= + cz. 18000 IN RRSIG DNSKEY 10 1 18000 20170304000000 20170218000000 54576 cz. paDUYJRI+4qBfPaGBy7nVMQnsp2hQQdiWWMnNunhfemFYi9MtXE2VTG3 DDL4Kue3ImSko/BxCRqHxHq5Sdf4LNexFWqFUlz4CjVeFobGTmmgOlak Sm2WygfZsO3w1OeO5cDCZTbi6XAhkr1cL3sgJR+/aOKIGUs8uIk1pZ5H WGNB1waF7Euxe+joEFtoj2/Tk7G7AlD1/Hw+pw5AkLTNawpHJF1/vnfT mPxdPHhJYCHlQdBE9dLkqQk7swnxMegBiUCeRd7SRiGq+1wubYsGirwl RZfYQpcqMnLH/1KITlVkKNYKnUGLjej4XRCDZOe3j8geIyS7WCJ5OPnU Lw0KDA== + cz. 18000 IN RRSIG DNSKEY 10 1 18000 20170313144128 20170228113958 58211 cz. xSEKl8ttuDR9Q3YjtVX+dPfdtwd4OG6rooml9TDIKNlND9LRTceRnpEH EsxUumTrRfWh8P4HWZF+B7hdm8qvcxAS3X3TYT4T7fKV5AFQbbMh+fv9 nut2RcZF40/x/0Hxh6QPLAtMDZs4W8IovQnpiTw8am9UoJNP+tT+dsgw ndA= + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id copy_query + REPLY QR AA RD DO NOERROR + SECTION QUESTION + nic.cz. IN DNSKEY + SECTION ANSWER + nic.cz. 1800 IN DNSKEY 256 3 13 vtFRotT17dIOLFIWi8BVFpHu8Thf/BrslFNNWlH2PPucF1rec69vuJi2 MswwoRtYQpRehbsjsjJ7kxXlTtfaFw== + nic.cz. 1800 IN DNSKEY 257 3 13 LM4zvjUgZi2XZKsYooDE0HFYGfWp242fKB+O8sLsuox8S6MJTowY8lBD jZD7JKbmaNot3+1H8zU9TrDzWmmHwQ== + nic.cz. 1800 IN RRSIG DNSKEY 13 2 1800 20170313103655 20170228072511 61281 nic.cz. mA899bEiTCULWpuF2JpVSm3wyHWmHIYuRMJj2X2E0AUhdbX2zhuSun8q EjKpr/0FfZCmlJIEC6dXmjIV+X0jhg== + nic.cz. 1800 IN RRSIG DNSKEY 13 2 1800 20170313194411 20170228072511 16836 nic.cz. iYJgEoykgdz6aqrE1DwM6fyWUFI2pDShqgfg7TiMaunyuvi2JwUaSbEq Ifm2aO5gF7bqSQjM+Y0NOzZ5nAUKrg== + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO NOERROR + SECTION QUESTION + lidovky.cz. IN DS + SECTION ANSWER + lidovky.cz. 14400 IN DS 1901 8 2 1ED680FFBD77C4845A9BE15286FC73A756B6E4150C65DBC52EE4799B 641DFCE3 + lidovky.cz. 14400 IN DS 4555 8 2 E4B03345B8E0EB3CD9208D2FA60F835A1E391CC485E84CBF3CB1136B D7748913 + lidovky.cz. 14400 IN RRSIG DS 10 2 14400 20170312182850 20170228113958 58211 cz. yw/iboH4hKxLOv+0Mbyvp4rnT14IxkiOpk6kW7ANJI2AGoBa5L7oGy6F 4eEuc2AZKrn/FP2OZL8mItt0hBCucHpaBeRyx8n78pCuMnEaYs/Buxro 0S/bpkMhTRTTJCQ2uwKHAAfi2Q3PC1CWLKB8p7MbN21JlC3S7ANu0DgL 4Ro= + ENTRY_END + + ENTRY_BEGIN + MATCH opcode subdomain + ADJUST copy_id copy_query + REPLY QR RD DO NOERROR + SECTION QUESTION + lidovky.cz. IN NS + SECTION AUTHORITY + lidovky.cz. 14400 IN NS ns.mafra.cz. + lidovky.cz. 14400 IN NS ns.mafracz.net. + lidovky.cz. 14400 IN NS ns2.mafra.cz. + lidovky.cz. 14400 IN DS 1901 8 2 1ED680FFBD77C4845A9BE15286FC73A756B6E4150C65DBC52EE4799B 641DFCE3 + lidovky.cz. 14400 IN DS 4555 8 2 E4B03345B8E0EB3CD9208D2FA60F835A1E391CC485E84CBF3CB1136B D7748913 + lidovky.cz. 14400 IN RRSIG DS 10 2 14400 20170312182850 20170228113958 58211 cz. yw/iboH4hKxLOv+0Mbyvp4rnT14IxkiOpk6kW7ANJI2AGoBa5L7oGy6F 4eEuc2AZKrn/FP2OZL8mItt0hBCucHpaBeRyx8n78pCuMnEaYs/Buxro 0S/bpkMhTRTTJCQ2uwKHAAfi2Q3PC1CWLKB8p7MbN21JlC3S7ANu0DgL 4Ro= + SECTION ADDITIONAL + ns.mafra.cz. 18000 IN A 194.79.53.77 + ns2.mafra.cz. 18000 IN A 194.79.55.77 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO NOERROR + SECTION QUESTION + mafra.cz. IN DS + SECTION AUTHORITY + cz. 900 IN SOA a.ns.nic.cz. hostmaster.nic.cz. 1488285598 900 300 604800 900 + cz. 900 IN RRSIG SOA 10 1 14400 20170314055727 20170228113958 58211 cz. LBUALMOXd40KawVmUNWNlfMbeR0LDwNI5DPu9HqF8rtWCtHxReSGPrYs lyaL6gzVOn+i9Zikolj2arR+XPrb3vDMBjhh1AhP83p6Dfk4z0nEeaVy bJBdCSrcXcOi72RXY1QpO6lfhkpW2rhYtKS0Pq0rPVSF6rFVSLMavD82 X9s= + NP199O12UJ32S0N5CTA47VUUQK1B2N6P.cz. 900 IN NSEC3 1 0 10 34817B0B5673BB5D NP19M6SR9GQ4GR722R31PHMCCMV2L47C NS + NP199O12UJ32S0N5CTA47VUUQK1B2N6P.cz. 900 IN RRSIG NSEC3 10 2 900 20170309110321 20170224213957 58211 cz. Brz4hpl2jq+rhJlu9tZ6Ij0Ru4+2Yyw5a4OVgN4/umq/9jPn2dWgnOPS 6Mk5WIC9Yun9ZIvncS3oE1dRhXAF+nGZS9jr1tdXLx+1Sow4o0nP8cxw 8Sl8BVjBkDpVSZfGVMN06NjJub57uw5nDF3E/AjoCYDxnb0UrVmIGCUb h7A= + ENTRY_END + + ENTRY_BEGIN + MATCH opcode subdomain + ADJUST copy_id copy_query + REPLY QR RD DO NOERROR + SECTION QUESTION + mafra.cz. IN NS + SECTION AUTHORITY + mafra.cz. 14400 IN NS ns.mafra.cz. + mafra.cz. 14400 IN NS ns.mafracz.net. + mafra.cz. 14400 IN NS ns2.mafra.cz. + np199o12uj32s0n5cta47vuuqk1b2n6p.cz. 900 IN NSEC3 1 0 10 34817B0B5673BB5D NP19M6SR9GQ4GR722R31PHMCCMV2L47C NS + np199o12uj32s0n5cta47vuuqk1b2n6p.cz. 900 IN RRSIG NSEC3 10 2 900 20170309110321 20170224213957 58211 cz. Brz4hpl2jq+rhJlu9tZ6Ij0Ru4+2Yyw5a4OVgN4/umq/9jPn2dWgnOPS 6Mk5WIC9Yun9ZIvncS3oE1dRhXAF+nGZS9jr1tdXLx+1Sow4o0nP8cxw 8Sl8BVjBkDpVSZfGVMN06NjJub57uw5nDF3E/AjoCYDxnb0UrVmIGCUb h7A= + SECTION ADDITIONAL + ns.mafra.cz. 7275 IN A 194.79.53.77 + ns2.mafra.cz. 7275 IN A 194.79.55.77 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO NOERROR + SECTION QUESTION + idnes.cz. IN DS + SECTION AUTHORITY + cz. 900 IN SOA a.ns.nic.cz. hostmaster.nic.cz. 1488285598 900 300 604800 900 + cz. 900 IN RRSIG SOA 10 1 14400 20170314055727 20170228113958 58211 cz. LBUALMOXd40KawVmUNWNlfMbeR0LDwNI5DPu9HqF8rtWCtHxReSGPrYs lyaL6gzVOn+i9Zikolj2arR+XPrb3vDMBjhh1AhP83p6Dfk4z0nEeaVy bJBdCSrcXcOi72RXY1QpO6lfhkpW2rhYtKS0Pq0rPVSF6rFVSLMavD82 X9s= + AUOICN1434M9JOGSCEGTCFV7NUDKO603.cz. 900 IN NSEC3 1 0 10 34817B0B5673BB5D AUOJ570J8RB3057RHUJ1DAGMCO1GAUDH NS + AUOICN1434M9JOGSCEGTCFV7NUDKO603.cz. 900 IN RRSIG NSEC3 10 2 900 20170313031226 20170227134003 58211 cz. CMvsPy0Ce7UR692R7jMat7E9Mm2DHTcZz7b5PlwsNX3i+41Ymlh1TeAs utrGbJUR+cdKQStzN6uNsxGQ84zFmeqOvMKtZBbvdavQbXtDfwTuEplX XolQ82j/0wVYCkpYANkLmyLrwbbZxJ4sSb1sbVRtMN0daeE6y3OleQDk 2Uw= + ENTRY_END + + ENTRY_BEGIN + MATCH opcode subdomain + ADJUST copy_id copy_query + REPLY QR RD DO NOERROR + SECTION QUESTION + idnes.cz. IN NS + SECTION AUTHORITY + idnes.cz. 14400 IN NS ns.mafra.cz. + idnes.cz. 14400 IN NS ns.mafracz.net. + idnes.cz. 14400 IN NS ns2.mafra.cz. + auoicn1434m9jogscegtcfv7nudko603.cz. 900 IN NSEC3 1 0 10 34817B0B5673BB5D AUOJ570J8RB3057RHUJ1DAGMCO1GAUDH NS + auoicn1434m9jogscegtcfv7nudko603.cz. 900 IN RRSIG NSEC3 10 2 900 20170313031226 20170227134003 58211 cz. CMvsPy0Ce7UR692R7jMat7E9Mm2DHTcZz7b5PlwsNX3i+41Ymlh1TeAs utrGbJUR+cdKQStzN6uNsxGQ84zFmeqOvMKtZBbvdavQbXtDfwTuEplX XolQ82j/0wVYCkpYANkLmyLrwbbZxJ4sSb1sbVRtMN0daeE6y3OleQDk 2Uw= + SECTION ADDITIONAL + ns.mafra.cz. 18000 IN A 194.79.53.77 + ns2.mafra.cz. 18000 IN A 194.79.55.77 + ENTRY_END + +RANGE_END + +;net +RANGE_BEGIN 0 10000 + ADDRESS 192.5.6.30 + ADDRESS 192.33.14.30 + ADDRESS 192.26.92.30 + ADDRESS 192.31.80.30 + ADDRESS 192.12.94.30 + ADDRESS 192.35.51.30 + ADDRESS 192.42.93.30 + ADDRESS 192.54.112.30 + ADDRESS 192.43.172.30 + ADDRESS 192.48.79.30 + ADDRESS 192.52.178.30 + ADDRESS 192.41.162.30 + ADDRESS 192.55.83.30 + ADDRESS 2001:503:a83e::2:30 + ADDRESS 2001:503:231d::2:30 + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id copy_query + REPLY QR AA RD DO NOERROR + SECTION QUESTION + net. IN DNSKEY + SECTION ANSWER + net. 86400 IN DNSKEY 257 3 8 AQOYBnzqWXIEj6mlgXg4LWC0HP2n8eK8XqgHlmJ/69iuIHsa1TrHDG6T cOra/pyeGKwH0nKZhTmXSuUFGh9BCNiwVDuyyb6OBGy2Nte9Kr8NwWg4 q+zhSoOf4D+gC9dEzg0yFdwT0DKEvmNPt0K4jbQDS4Yimb+uPKuF6yie WWrPYYCrv8C9KC8JMze2uT6NuWBfsl2fDUoV4l65qMww06D7n+p7Rbdw WkAZ0fA63mXVXBZF6kpDtsYD7SUB9jhhfLQE/r85bvg3FaSs5Wi2BaqN 06SzGWI1DHu7axthIOeHwg00zxlhTpoYCH0ldoQz+S65zWYi/fRJiyLS Bb6JZOvn + net. 86400 IN DNSKEY 256 3 8 AQPMYWRP6GrTFoGFNQyuta0p4VYHr5Ox7yOl0Zv5ejOeRUnmoVgvHUR0 8lmmKEnBBPPZ89f/spt8VQ3GFUAbjJVzlcF5dQbY26YO/XKNcB2dlCEy quowoOQYsbASUj91c0IfFXAbK10reyShzaUi76p2VG5f0tjq/iC4iMZJ yxcpRQ== + net. 86400 IN RRSIG DNSKEY 8 1 86400 20170306173857 20170219173357 35886 net. Vvmjg9riU5c81z+4GEMSV4kEHf0ds2lxyD/UmGB4Vjtu0S71KldD4hh2 nA086G2Ssl1gBFEcVkLdPPpvh/c39mSITollT43u55pBLGQQcRXqPL6X 5xjlsOayD4QfwszBn5/5QTSD9pB5D9AsGQARlQTa0Vp1O9ruFDq0BuVQ F4P2QkNaxM6T+QZdFtqFOe6n3H+Qn0/TEvbM72w0hIBr1po3aSZuJleN SR3Wbubs1H7p1E6a6FH2+rRb3t7Q5DWNT/P5kZU0j+JB1PRknSwWCv7n orxIfhoYuFqU8Gw9w5KSw+Qtc7AjxlawQSAAZPLaq9ZL2cEKkeUrEGTD V41adg== + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO NOERROR + SECTION QUESTION + net. IN DS + SECTION ANSWER + net. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1488288307 1800 900 604800 86400 + net. 900 IN RRSIG SOA 8 1 900 20170307132507 20170228121507 16757 net. aeKdMkRb/POrt2pw0h0O8fN8EUXFXJlPHu/aHtIihIEkj85ZpCNrEOxr Zg5jkYtPXQwx+X0cnD/uNMEWPOD3vNW3Ap9Y01RlFBzvlBHeH4YA09tr ElBPqkzN6bNrNJi3V/yJjV2dy7IUvqDO9M5cQEuPHIED2sIh1FATmB6b KMs= + A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN NSEC3 1 1 0 - A1RUUFFJKCT2Q54P78F8EJGJ8JBK7I8B NS SOA RRSIG DNSKEY NSEC3PARAM + A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN RRSIG NSEC3 8 2 86400 20170306061207 20170227050207 16757 net. s53ftACmRAtcKkfowIENgWkCuHNoyesDp5kz1g62Uxm9v03ig4TkMMBW cUMvLFCp1XpmiOx9MX5klfJgFrhQPYmaRBuQaI3nrH6B57kjsphtJYvc B6wyRGPHAg+oNecZqQbUBEkzBrppoe4a5nhlOkLgbHKb5qPbN0tV5wBu x5c= + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO AA NOERROR + SECTION QUESTION + net. IN NS + SECTION ANSWER + net. 172800 IN NS i.gtld-servers.net. + net. 172800 IN NS e.gtld-servers.net. + net. 172800 IN NS h.gtld-servers.net. + net. 172800 IN NS b.gtld-servers.net. + net. 172800 IN NS a.gtld-servers.net. + net. 172800 IN NS j.gtld-servers.net. + net. 172800 IN NS f.gtld-servers.net. + net. 172800 IN NS m.gtld-servers.net. + net. 172800 IN NS l.gtld-servers.net. + net. 172800 IN NS g.gtld-servers.net. + net. 172800 IN NS k.gtld-servers.net. + net. 172800 IN NS c.gtld-servers.net. + net. 172800 IN NS d.gtld-servers.net. + net. 172800 IN RRSIG NS 8 1 172800 20170304061505 20170225050505 16757 net. Pq4fze7lagq5NaKm7P4plOCY4gbFH3ZqZPvWIMojqNgHmoboqXWpth7R s2th1NzR7fxTvxngwVFlO7tR2Sf19epNimuJHEkxAKceLtSfdwxilfMz WvPq5/2tCINU8xo/SOC13ST4zq3PUi+VfPYbRF+5SakOTkU/6m1+9hlo ixo= + SECTION ADDITIONAL + a.gtld-servers.net. 172800 IN A 192.5.6.30 + b.gtld-servers.net. 172800 IN A 192.33.14.30 + c.gtld-servers.net. 172800 IN A 192.26.92.30 + d.gtld-servers.net. 172800 IN A 192.31.80.30 + e.gtld-servers.net. 172800 IN A 192.12.94.30 + f.gtld-servers.net. 172800 IN A 192.35.51.30 + g.gtld-servers.net. 172800 IN A 192.42.93.30 + h.gtld-servers.net. 172800 IN A 192.54.112.30 + i.gtld-servers.net. 172800 IN A 192.43.172.30 + j.gtld-servers.net. 172800 IN A 192.48.79.30 + k.gtld-servers.net. 172800 IN A 192.52.178.30 + l.gtld-servers.net. 172800 IN A 192.41.162.30 + m.gtld-servers.net. 172800 IN A 192.55.83.30 + a.gtld-servers.net. 172800 IN AAAA 2001:503:a83e::2:30 + b.gtld-servers.net. 172800 IN AAAA 2001:503:231d::2:30 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO NOERROR + SECTION QUESTION + root-servers.net. IN DS + SECTION AUTHORITY + A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN NSEC3 1 1 0 - A1RUUFFJKCT2Q54P78F8EJGJ8JBK7I8B NS SOA RRSIG DNSKEY NSEC3PARAM + A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN RRSIG NSEC3 8 2 86400 20170306061207 20170227050207 16757 net. s53ftACmRAtcKkfowIENgWkCuHNoyesDp5kz1g62Uxm9v03ig4TkMMBW cUMvLFCp1XpmiOx9MX5klfJgFrhQPYmaRBuQaI3nrH6B57kjsphtJYvc B6wyRGPHAg+oNecZqQbUBEkzBrppoe4a5nhlOkLgbHKb5qPbN0tV5wBu x5c= + net. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1488288412 1800 900 604800 86400 + net. 900 IN RRSIG SOA 8 1 900 20170307132652 20170228121652 16757 net. VnpLNPVwJO8pW1+aHC5jGq17aTVQuWMfu7igBcig9XG9li1wVBtawqaB zpiT8zoUWa76qkydyhuKWNjR895eLQz1Ql0cboW8GIddDFfKacpEP9nr QWwqjiMltfXn+iGiumrDbxwHKvwllXhOIShR5uAT640UcJ7QMhrq2jrJ V+Y= + T2UFL481TTPOHR68HR18DHJAFU935MJU.net. 86400 IN NSEC3 1 1 0 - T2UKCT9K5I0UHV7B3M3NA6JAIGDJM0GR NS DS RRSIG + T2UFL481TTPOHR68HR18DHJAFU935MJU.net. 86400 IN RRSIG NSEC3 8 2 86400 20170307061346 20170228050346 16757 net. KpGr8ZrjFGZ2q39FPpGe9SBR4hJ1e8L9oyvO5JS7Eh4LVdjwsD8B13nQ 7iv6jdCWVIWXh41fB4dcCUvLYqd9d75bACQ4JQVR3ycON9Qwt2XiUyVk iBYm7cp9C78+Uj0/P3TClk90GtZaAb3+JXUZZvrK08HnivVtmTta1Laj TVk= + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO NOERROR + SECTION QUESTION + gtld-servers.net. IN DS + SECTION AUTHORITY + A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN NSEC3 1 1 0 - A1RUUFFJKCT2Q54P78F8EJGJ8JBK7I8B NS SOA RRSIG DNSKEY NSEC3PARAM + A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN RRSIG NSEC3 8 2 86400 20170306061207 20170227050207 16757 net. s53ftACmRAtcKkfowIENgWkCuHNoyesDp5kz1g62Uxm9v03ig4TkMMBW cUMvLFCp1XpmiOx9MX5klfJgFrhQPYmaRBuQaI3nrH6B57kjsphtJYvc B6wyRGPHAg+oNecZqQbUBEkzBrppoe4a5nhlOkLgbHKb5qPbN0tV5wBu x5c= + net. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1488288457 1800 900 604800 86400 + net. 900 IN RRSIG SOA 8 1 900 20170307132737 20170228121737 16757 net. x5j/Iiv9Bey7T4FSeICdJrAyn5tEubHlhQXGmjM4KAVEq1CybM70rL7s HrwAhyiC/9RobYaMhM4fxmji3h8vWYbWauGMZ5XXmRGL66jE6Zq/M99v zk7RDnedNS+vPAv49PJ5aICGs4hfapPg3Kwf/KKwDzzvactaRYPvptLX u74= + 5QD8VL68T2I9KOBD32KJ8LJVH5OH2PQ0.net. 86400 IN NSEC3 1 1 0 - 5QDPPOTUK27KKP9LIGTRB0K1CBVM9CIM NS DS RRSIG + 5QD8VL68T2I9KOBD32KJ8LJVH5OH2PQ0.net. 86400 IN RRSIG NSEC3 8 2 86400 20170306060531 20170227045531 16757 net. uV9O+X7Vk1+dgIdqY2qE5RvN4B4Nv+xDLjd5V30sapNI8ARrA8d9pEVY qGNU5tF8+VT3lukCjvfgfopyTjw+SO+x4fwpZenmehwgNFkMHYWAv/1l xrdZHw60JMa/jWy+Rtdqi2uBJMGldGEIiuLEHgkKAjub2wtdiEkl2Azo AeY= + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype subdomain + ADJUST copy_id copy_query + REPLY QR RD DO NOERROR + SECTION QUESTION + gtld-servers.net. IN NS + + SECTION AUTHORITY + gtld-servers.net. 172800 IN NS av1.nstld.com. + gtld-servers.net. 172800 IN NS av2.nstld.com. + gtld-servers.net. 172800 IN NS av3.nstld.com. + gtld-servers.net. 172800 IN NS av4.nstld.com. + A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN NSEC3 1 1 0 - A1RUUFFJKCT2Q54P78F8EJGJ8JBK7I8B NS SOA RRSIG DNSKEY NSEC3PARAM + A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN RRSIG NSEC3 8 2 86400 20170306061207 20170227050207 16757 net. s53ftACmRAtcKkfowIENgWkCuHNoyesDp5kz1g62Uxm9v03ig4TkMMBW cUMvLFCp1XpmiOx9MX5klfJgFrhQPYmaRBuQaI3nrH6B57kjsphtJYvc B6wyRGPHAg+oNecZqQbUBEkzBrppoe4a5nhlOkLgbHKb5qPbN0tV5wBu x5c= + 5QD8VL68T2I9KOBD32KJ8LJVH5OH2PQ0.net. 86400 IN NSEC3 1 1 0 - 5QDPPOTUK27KKP9LIGTRB0K1CBVM9CIM NS DS RRSIG + 5QD8VL68T2I9KOBD32KJ8LJVH5OH2PQ0.net. 86400 IN RRSIG NSEC3 8 2 86400 20170306060531 20170227045531 16757 net. uV9O+X7Vk1+dgIdqY2qE5RvN4B4Nv+xDLjd5V30sapNI8ARrA8d9pEVY qGNU5tF8+VT3lukCjvfgfopyTjw+SO+x4fwpZenmehwgNFkMHYWAv/1l xrdZHw60JMa/jWy+Rtdqi2uBJMGldGEIiuLEHgkKAjub2wtdiEkl2Azo AeY= + + SECTION ADDITIONAL + av1.nstld.com. 172800 IN A 192.42.177.30 + av1.nstld.com. 172800 IN AAAA 2001:500:124::30 + av2.nstld.com. 172800 IN A 192.42.178.30 + av2.nstld.com. 172800 IN AAAA 2001:500:125::30 + av3.nstld.com. 172800 IN A 192.82.133.30 + av3.nstld.com. 172800 IN AAAA 2001:500:126::30 + av4.nstld.com. 172800 IN A 192.82.134.30 + av4.nstld.com. 172800 IN AAAA 2001:500:127::30 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qname qtype + ADJUST copy_id + REPLY QR RD DO NOERROR + SECTION QUESTION + a.gtld-servers.net. IN A + SECTION ANSWER + a.gtld-servers.net. 172800 IN A 192.5.6.30 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode subdomain + ADJUST copy_id copy_query + REPLY QR RD DO NOERROR + SECTION QUESTION + gtld-servers.net. IN A + SECTION AUTHORITY + gtld-servers.net. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2016101000 3600 900 1209600 86400 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id copy_query + REPLY QR RD DO NOERROR + SECTION QUESTION + a.root-servers.net. IN A + SECTION AUTHORITY + root-servers.net. 172800 IN NS a.root-servers.net. + SECTION ADDITIONAL + a.root-servers.net. 3600000 IN A 198.41.0.4 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode subdomain + ADJUST copy_id copy_query + REPLY QR RD DO NOERROR + SECTION QUESTION + root-servers.net. IN AAAA + SECTION AUTHORITY + root-servers.net. 172800 IN NS a.root-servers.net. + SECTION ADDITIONAL + a.root-servers.net. 172800 IN A 198.41.0.4 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO NOERROR + SECTION QUESTION + mafracz.net. IN DS + SECTION AUTHORITY + A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN NSEC3 1 1 0 - A1RUUFFJKCT2Q54P78F8EJGJ8JBK7I8B NS SOA RRSIG DNSKEY NSEC3PARAM + A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN RRSIG NSEC3 8 2 86400 20170306061207 20170227050207 16757 net. s53ftACmRAtcKkfowIENgWkCuHNoyesDp5kz1g62Uxm9v03ig4TkMMBW cUMvLFCp1XpmiOx9MX5klfJgFrhQPYmaRBuQaI3nrH6B57kjsphtJYvc B6wyRGPHAg+oNecZqQbUBEkzBrppoe4a5nhlOkLgbHKb5qPbN0tV5wBu x5c= + net. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1488288547 1800 900 604800 86400 + net. 900 IN RRSIG SOA 8 1 900 20170307132907 20170228121907 16757 net. y7pu7BBxAqE7l+JB4PIg/2l+WPgeOrSo+TRo2vKqVJFa03GttNi4BlWH s0sT3t4Mr0nvWxNf7PlUUct7KFssGGCu6kXC3RiZsXDaTeAnHjAfk9rg v/z6PM7fU3shLjEXDuIY9GtPAw65nbSeK1Sai/3gWUOnlxo1J2r3VXl3 cfE= + P61KBBD5BIIR8OO46HQUMTGEQAU7RAQJ.net. 86400 IN NSEC3 1 1 0 - P61TM41BB9FNGTRQ6D1PPAU0E9MD6S63 NS DS RRSIG + P61KBBD5BIIR8OO46HQUMTGEQAU7RAQJ.net. 86400 IN RRSIG NSEC3 8 2 86400 20170304061336 20170225050336 16757 net. QKFFK4L57Pzylgc3d/9Z5R++Cqxx5agyEG6HPcGtjCSslA7DEj+qULoy TTWNBpgzPgwwrZy0BdNYBZdC3rpdfiJqCidVXe7bRfUQDHY4NJiuouOv jLGxYf/k8gqKAElV9CriTBkkjALwXdlDvCSMnhczMlu0409YoL3XKBdE TCc= + ENTRY_END + + ENTRY_BEGIN + MATCH opcode subdomain + ADJUST copy_id copy_query + REPLY QR RD DO NOERROR + SECTION QUESTION + mafracz.net. IN NS + SECTION AUTHORITY + mafracz.net. 172800 IN NS ns.mafra.cz. + mafracz.net. 172800 IN NS ns2.mafra.cz. + mafracz.net. 172800 IN NS ns.mafracz.net. + A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN NSEC3 1 1 0 - A1RUUFFJKCT2Q54P78F8EJGJ8JBK7I8B NS SOA RRSIG DNSKEY NSEC3PARAM + A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN RRSIG NSEC3 8 2 86400 20170306061207 20170227050207 16757 net. s53ftACmRAtcKkfowIENgWkCuHNoyesDp5kz1g62Uxm9v03ig4TkMMBW cUMvLFCp1XpmiOx9MX5klfJgFrhQPYmaRBuQaI3nrH6B57kjsphtJYvc B6wyRGPHAg+oNecZqQbUBEkzBrppoe4a5nhlOkLgbHKb5qPbN0tV5wBu x5c= + P61KBBD5BIIR8OO46HQUMTGEQAU7RAQJ.net. 86400 IN NSEC3 1 1 0 - P61TM41BB9FNGTRQ6D1PPAU0E9MD6S63 NS DS RRSIG + P61KBBD5BIIR8OO46HQUMTGEQAU7RAQJ.net. 86400 IN RRSIG NSEC3 8 2 86400 20170304061336 20170225050336 16757 net. QKFFK4L57Pzylgc3d/9Z5R++Cqxx5agyEG6HPcGtjCSslA7DEj+qULoy TTWNBpgzPgwwrZy0BdNYBZdC3rpdfiJqCidVXe7bRfUQDHY4NJiuouOv jLGxYf/k8gqKAElV9CriTBkkjALwXdlDvCSMnhczMlu0409YoL3XKBdE TCc= + SECTION ADDITIONAL + ns.mafracz.net. 165236 IN A 185.17.118.250 + ENTRY_END +RANGE_END + + +RANGE_BEGIN 0 10000 + ADDRESS 194.79.53.77 + ADDRESS 185.17.118.250 + ADDRESS 194.79.55.77 + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id copy_query + REPLY QR AA RD DO NOERROR + SECTION QUESTION + ns.mafracz.net. IN A + SECTION ANSWER + ns.mafracz.net. 600 IN A 185.17.118.250 + SECTION AUTHORITY + mafracz.net. 600 IN NS ns2.mafra.cz. + mafracz.net. 600 IN NS ns.mafracz.net. + mafracz.net. 600 IN NS ns.mafra.cz. + SECTION ADDITIONAL + ns.mafra.cz. 300 IN A 194.79.53.77 + ns2.mafra.cz. 300 IN A 194.79.55.77 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id copy_query + REPLY QR AA RD DO NOERROR + SECTION QUESTION + lidovky.cz. IN DNSKEY + SECTION ANSWER + lidovky.cz. 3600 IN DNSKEY 256 3 8 AwEAAb8GYuVqOqVTYuppOCtctAHJ38tuSVriiptyQcxdZsU7U7s2XFVp QAuTxxoBOuvJZLMLXNikOki1KCnQx030Zz73AFx8tiPS6rFzR71TJXTC HlpwDnnK3rkdsu6Ay85cLiRtFpgW1D1WPi5oCJWGs4dJ8L5mcoIYikZt 99cfDKY/ + lidovky.cz. 3600 IN DNSKEY 256 3 8 AwEAActfDtlHpl0/2f9qMqDt5uslMzxKDNf4FGklmcG+OO2HuhOhnQVx arB6lYxIKofy+uOvUhyGxlxCq08bVKueBpAt0x5kLMAwhc6zmn8niIE6 +UZxLg7+r2ojLTl0qQ2sMoG6ryo4/1GCEwh/TjJp8PuAzE0Q7yQOE6ed jZkWjraJ + lidovky.cz. 3600 IN DNSKEY 257 3 8 AwEAAeFABHDi1QXB2WaYeLP07RzSfn9IIjMFrL6+obHNgMpY32skT0fX +4YiF1vrAwI3FyvqvLERcUqZl3kMFk/mBDEBcCCP8osbndEUEEg2fVkZ gPDVWT3nCBMXRRuXmddn+L7o18wTUTBbLCxCT22ROOqahUyDEHvHpUbq LTbY+GGnSNzAD9/BWFdMIGOKzQ8oYFFyWDGZYAcznojZO7gvpduw3slg t3YLv4iDxMIgFokCw+qQhf42xtmox7H6KfCaW59PdFfRRAc20JfpGxJ4 m2PAuuacgOoVkqRLqprJ0/NCmMJgQZ3yKQWe2QWfRP9lhmF9HXAVukyy Yh3+JaqulxM= + lidovky.cz. 3600 IN RRSIG DNSKEY 8 2 3600 20170305034217 20170226010009 1901 lidovky.cz. ElBtNV7iyYIWDExYkKJ+pwwIcSwJ6kXfiT3yFiwp43CqXg9KxMK55UBe nCToid81/xgGQmSnmHw8w5LQXs5CjiIamoMYCX0SCie0FsfvFx1871np CvzTeSr4U876wnZVAjmM/FnDP63/4SgIICZpMb3P/MU7M+zr93JgOMXs E0Zp4uR6puh7a52VMRBLBIEx4L8mw2TW3VU9an2FD5r6GnAqI5YqEY8P FpHdkb243AvB3rZBWtDiKFSzD+WsrqrDOL3lmA/Jcb5GcxA2CGxfTSCJ +ndebgdkFSwPXQxW7FQwdS4mTuPixdzonq8XtljLZSomyJ0mnepn0j7k lwklow== + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR AA RD DO NOERROR + SECTION QUESTION + lidovky.cz. IN A + SECTION ANSWER + lidovky.cz. 300 IN A 185.17.119.32 + lidovky.cz. 300 IN RRSIG A 8 2 300 20170306080352 20170227090009 61408 lidovky.cz. rDSYYHIlE4Drq4/QLXrTDk+oeY6nh+W0p7cSeH9BGojdE4qIHjWUDjOS C4sEQpZtgG6EKO5j6P2+8bJ/3SmWdFT2GPHgP5eeRHPoo3iaGQMxXebD pbyVHtN//Gb577ycKcbNys/loflzhTWL2K1QXIHk53iWOTlDBg6uJcqi HsI= + SECTION AUTHORITY + lidovky.cz. 300 IN NS ns2.mafra.cz. + lidovky.cz. 300 IN NS ns.mafracz.net. + lidovky.cz. 300 IN NS ns.mafra.cz. + lidovky.cz. 300 IN RRSIG NS 8 2 300 20170304164701 20170225230009 61408 lidovky.cz. QmaLuzIDTiB/QbIgyxPRTVGFG/P5wFyrzlBtK7LIUsVIk8wuM9GudvQx weBiLPbaj28YypIdkS/z12sIawYenv4R9lswSVCOqT2H1KhXMtbW+BMk p5bCyr1mEJfceas6td4gywOydtfjYwU7WBvFPpMszP22p7jrizeQQpNB dK4= + SECTION ADDITIONAL + ns.mafra.cz. 300 IN A 194.79.53.77 + ns.mafracz.net. 600 IN A 185.17.118.250 + ns2.mafra.cz. 300 IN A 194.79.55.77 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR AA RD DO NOERROR + SECTION QUESTION + www.lidovky.cz. IN A + SECTION ANSWER + www.lidovky.cz. 300 IN CNAME c23.idnes.cz. + www.lidovky.cz. 300 IN RRSIG CNAME 8 3 300 20170305033947 20170226030009 61408 lidovky.cz. VyqkB8Fzxs+LTz9WDKLMmbyrtq+V/5R1sYfYBQJPuVa3pJ1vX2I5M6XK n7TDu9gsW2v+zquOps/8aL/e/+R8ivEJomYzdnvH3EwfgT9WCOYJtlUL +sIq8eu45jXTVsFVLa0Fy5LKeFcfic+4C6AG676o5VSucVJLTWiftW47 RPA= + c23.idnes.cz. 300 IN A 185.17.119.54 + SECTION AUTHORITY + idnes.cz. 300 IN NS ns.mafra.cz. + idnes.cz. 300 IN NS ns2.mafra.cz. + idnes.cz. 300 IN NS ns.mafracz.net. + SECTION ADDITIONAL + ns.mafra.cz. 300 IN A 194.79.53.77 + ns.mafracz.net. 600 IN A 185.17.118.250 + ns2.mafra.cz. 300 IN A 194.79.55.77 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR AA RD DO NOERROR + SECTION QUESTION + c23.idnes.cz. IN A + SECTION ANSWER + c23.idnes.cz. 300 IN A 185.17.119.54 + SECTION AUTHORITY + idnes.cz. 300 IN NS ns.mafra.cz. + idnes.cz. 300 IN NS ns2.mafra.cz. + idnes.cz. 300 IN NS ns.mafracz.net. + SECTION ADDITIONAL + ns.mafra.cz. 300 IN A 194.79.53.77 + ns.mafracz.net. 600 IN A 185.17.118.250 + ns2.mafra.cz. 300 IN A 194.79.55.77 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR AA RD DO NOERROR + SECTION QUESTION + ns2.mafra.cz. IN A + SECTION ANSWER + ns2.mafra.cz. 300 IN A 194.79.55.77 + SECTION AUTHORITY + mafra.cz. 300 IN NS ns.mafra.cz. + mafra.cz. 300 IN NS ns2.mafra.cz. + mafra.cz. 300 IN NS ns.mafracz.net. + SECTION ADDITIONAL + ns.mafra.cz. 300 IN A 194.79.53.77 + ns.mafracz.net. 600 IN A 185.17.118.250 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR AA RD DO NOERROR + SECTION QUESTION + ns.mafra.cz. IN A + SECTION ANSWER + ns.mafra.cz. 300 IN A 194.79.53.77 + SECTION AUTHORITY + mafra.cz. 300 IN NS ns.mafra.cz. + mafra.cz. 300 IN NS ns2.mafra.cz. + mafra.cz. 300 IN NS ns.mafracz.net. + SECTION ADDITIONAL + ns2.mafra.cz. 300 IN A 194.79.55.77 + ns.mafracz.net. 600 IN A 185.17.118.250 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO AA NOERROR + SECTION QUESTION + lidovky.cz. IN NS + SECTION ANSWER + lidovky.cz. 300 IN NS ns.mafra.cz. + lidovky.cz. 300 IN NS ns2.mafra.cz. + lidovky.cz. 300 IN NS ns.mafracz.net. + lidovky.cz. 300 IN RRSIG NS 8 2 300 20170304164701 20170225230009 61408 lidovky.cz. QmaLuzIDTiB/QbIgyxPRTVGFG/P5wFyrzlBtK7LIUsVIk8wuM9GudvQx weBiLPbaj28YypIdkS/z12sIawYenv4R9lswSVCOqT2H1KhXMtbW+BMk p5bCyr1mEJfceas6td4gywOydtfjYwU7WBvFPpMszP22p7jrizeQQpNB dK4= + SECTION ADDITIONAL + ns.mafra.cz. 300 IN A 194.79.53.77 + ns.mafracz.net. 600 IN A 185.17.118.250 + ns2.mafra.cz. 300 IN A 194.79.55.77 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO AA NOERROR + SECTION QUESTION + mafra.cz. IN NS + SECTION ANSWER + mafra.cz. 300 IN NS ns.mafra.cz. + mafra.cz. 300 IN NS ns2.mafra.cz. + mafra.cz. 300 IN NS ns.mafracz.net. + SECTION ADDITIONAL + ns.mafra.cz. 300 IN A 194.79.53.77 + ns.mafracz.net. 600 IN A 185.17.118.250 + ns2.mafra.cz. 300 IN A 194.79.55.77 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO AA NOERROR + SECTION QUESTION + mafracz.net. IN NS + SECTION ANSWER + mafracz.net. 300 IN NS ns.mafra.cz. + mafracz.net. 300 IN NS ns2.mafra.cz. + mafracz.net. 300 IN NS ns.mafracz.net. + SECTION ADDITIONAL + ns.mafra.cz. 300 IN A 194.79.53.77 + ns.mafracz.net. 600 IN A 185.17.118.250 + ns2.mafra.cz. 300 IN A 194.79.55.77 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO AA NOERROR + SECTION QUESTION + idnes.cz. IN NS + SECTION ANSWER + idnes.cz. 300 IN NS ns2.mafra.cz. + idnes.cz. 300 IN NS ns.mafracz.net. + idnes.cz. 300 IN NS ns.mafra.cz. + + SECTION ADDITIONAL + ns.mafra.cz. 300 IN A 194.79.53.77 + ns.mafracz.net. 600 IN A 185.17.118.250 + ns2.mafra.cz. 300 IN A 194.79.55.77 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO NOERROR + SECTION QUESTION + ns.mafra.cz. IN AAAA + SECTION AUTHORITY + mafra.cz. 291 IN SOA ns.mafra.cz. hostmaster.mafra.cz. 2017021601 3600 600 1209600 600 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO NOERROR + SECTION QUESTION + ns2.mafra.cz. IN AAAA + SECTION AUTHORITY + mafra.cz. 291 IN SOA ns.mafra.cz. hostmaster.mafra.cz. 2017021601 3600 600 1209600 600 + ENTRY_END + + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD DO NOERROR + SECTION QUESTION + ns.mafracz.net. IN AAAA + SECTION AUTHORITY + mafracz.net. 600 IN SOA ns.mafracz.net. hostmaster.mafra.cz. 2015061701 3600 600 86400 3600 + ENTRY_END + +RANGE_END + +STEP 1011 QUERY +ENTRY_BEGIN + REPLY RD DO + SECTION QUESTION + ns.mafra.cz. IN A +ENTRY_END + +STEP 1012 CHECK_ANSWER +ENTRY_BEGIN + MATCH rcode question answer flags + REPLY QR RD RA NOERROR + SECTION QUESTION + ns.mafra.cz. IN A + SECTION ANSWER + ns.mafra.cz. 300 IN A 194.79.53.77 +ENTRY_END + + +STEP 1021 QUERY +ENTRY_BEGIN + REPLY RD DO + SECTION QUESTION + ns.mafracz.net. IN A +ENTRY_END + +STEP 1022 CHECK_ANSWER +ENTRY_BEGIN + MATCH rcode question answer flags + REPLY QR RD RA NOERROR + SECTION QUESTION + ns.mafracz.net. IN A + SECTION ANSWER + ns.mafracz.net. 600 IN A 185.17.118.250 +ENTRY_END + + +STEP 1031 QUERY +ENTRY_BEGIN + REPLY RD DO + SECTION QUESTION + www.lidovky.cz IN A +ENTRY_END + +STEP 1032 CHECK_ANSWER +ENTRY_BEGIN + MATCH rcode question answer flags + REPLY QR RD RA NOERROR + SECTION QUESTION + www.lidovky.cz IN A + SECTION ANSWER + www.lidovky.cz. 251 IN CNAME c23.idnes.cz. + www.lidovky.cz. 251 IN RRSIG CNAME 8 3 300 20170305033947 20170226030009 61408 lidovky.cz. VyqkB8Fzxs+LTz9WDKLMmbyrtq+V/5R1sYfYBQJPuVa3pJ1vX2I5M6XK n7TDu9gsW2v+zquOps/8aL/e/+R8ivEJomYzdnvH3EwfgT9WCOYJtlUL +sIq8eu45jXTVsFVLa0Fy5LKeFcfic+4C6AG676o5VSucVJLTWiftW47 RPA= + c23.idnes.cz. 251 IN A 185.17.119.54 +ENTRY_END + + +STEP 2011 QUERY +ENTRY_BEGIN + REPLY RD DO + SECTION QUESTION + ns.mafra.cz. IN A +ENTRY_END + +STEP 2012 CHECK_ANSWER +ENTRY_BEGIN + MATCH rcode question answer flags + REPLY QR RD RA NOERROR + SECTION QUESTION + ns.mafra.cz. IN A + SECTION ANSWER + ns.mafra.cz. 300 IN A 194.79.53.77 +ENTRY_END + + +STEP 2021 QUERY +ENTRY_BEGIN + REPLY RD DO + SECTION QUESTION + ns.mafracz.net. IN A +ENTRY_END + +STEP 2022 CHECK_ANSWER +ENTRY_BEGIN + MATCH rcode question answer flags + REPLY QR RD RA NOERROR + SECTION QUESTION + ns.mafracz.net. IN A + SECTION ANSWER + ns.mafracz.net. 600 IN A 185.17.118.250 +ENTRY_END + + +STEP 2031 QUERY +ENTRY_BEGIN + REPLY RD DO + SECTION QUESTION + www.lidovky.cz IN A +ENTRY_END + +STEP 2032 CHECK_ANSWER +ENTRY_BEGIN + MATCH rcode question answer flags + REPLY QR RD RA NOERROR + SECTION QUESTION + www.lidovky.cz IN A + SECTION ANSWER + www.lidovky.cz. 251 IN CNAME c23.idnes.cz. + www.lidovky.cz. 251 IN RRSIG CNAME 8 3 300 20170305033947 20170226030009 61408 lidovky.cz. VyqkB8Fzxs+LTz9WDKLMmbyrtq+V/5R1sYfYBQJPuVa3pJ1vX2I5M6XK n7TDu9gsW2v+zquOps/8aL/e/+R8ivEJomYzdnvH3EwfgT9WCOYJtlUL +sIq8eu45jXTVsFVLa0Fy5LKeFcfic+4C6AG676o5VSucVJLTWiftW47 RPA= + c23.idnes.cz. 251 IN A 185.17.119.54 +ENTRY_END + + +STEP 3011 QUERY +ENTRY_BEGIN + REPLY RD DO + SECTION QUESTION + ns.mafra.cz. IN A +ENTRY_END + +STEP 3012 CHECK_ANSWER +ENTRY_BEGIN + MATCH rcode question answer flags + REPLY QR RD RA NOERROR + SECTION QUESTION + ns.mafra.cz. IN A + SECTION ANSWER + ns.mafra.cz. 300 IN A 194.79.53.77 +ENTRY_END + + +STEP 3021 QUERY +ENTRY_BEGIN + REPLY RD DO + SECTION QUESTION + ns.mafracz.net. IN A +ENTRY_END + +STEP 3022 CHECK_ANSWER +ENTRY_BEGIN + MATCH rcode question answer flags + REPLY QR RD RA NOERROR + SECTION QUESTION + ns.mafracz.net. IN A + SECTION ANSWER + ns.mafracz.net. 600 IN A 185.17.118.250 +ENTRY_END + + +STEP 3031 QUERY +ENTRY_BEGIN + REPLY RD DO + SECTION QUESTION + www.lidovky.cz IN A +ENTRY_END + +STEP 3032 CHECK_ANSWER +ENTRY_BEGIN + MATCH rcode question answer flags + REPLY QR RD RA NOERROR + SECTION QUESTION + www.lidovky.cz IN A + SECTION ANSWER + www.lidovky.cz. 251 IN CNAME c23.idnes.cz. + www.lidovky.cz. 251 IN RRSIG CNAME 8 3 300 20170305033947 20170226030009 61408 lidovky.cz. VyqkB8Fzxs+LTz9WDKLMmbyrtq+V/5R1sYfYBQJPuVa3pJ1vX2I5M6XK n7TDu9gsW2v+zquOps/8aL/e/+R8ivEJomYzdnvH3EwfgT9WCOYJtlUL +sIq8eu45jXTVsFVLa0Fy5LKeFcfic+4C6AG676o5VSucVJLTWiftW47 RPA= + c23.idnes.cz. 251 IN A 185.17.119.54 +ENTRY_END + + +STEP 4011 QUERY +ENTRY_BEGIN + REPLY RD DO + SECTION QUESTION + ns.mafra.cz. IN A +ENTRY_END + +STEP 4012 CHECK_ANSWER +ENTRY_BEGIN + MATCH rcode question answer flags + REPLY QR RD RA NOERROR + SECTION QUESTION + ns.mafra.cz. IN A + SECTION ANSWER + ns.mafra.cz. 300 IN A 194.79.53.77 +ENTRY_END + + +STEP 4021 QUERY +ENTRY_BEGIN + REPLY RD DO + SECTION QUESTION + ns.mafracz.net. IN A +ENTRY_END + +STEP 4022 CHECK_ANSWER +ENTRY_BEGIN + MATCH rcode question answer flags + REPLY QR RD RA NOERROR + SECTION QUESTION + ns.mafracz.net. IN A + SECTION ANSWER + ns.mafracz.net. 600 IN A 185.17.118.250 +ENTRY_END + + +STEP 4031 QUERY +ENTRY_BEGIN + REPLY RD DO + SECTION QUESTION + www.lidovky.cz IN A +ENTRY_END + +STEP 4032 CHECK_ANSWER +ENTRY_BEGIN + MATCH rcode question answer flags + REPLY QR RD RA NOERROR + SECTION QUESTION + www.lidovky.cz IN A + SECTION ANSWER + www.lidovky.cz. 251 IN CNAME c23.idnes.cz. + www.lidovky.cz. 251 IN RRSIG CNAME 8 3 300 20170305033947 20170226030009 61408 lidovky.cz. VyqkB8Fzxs+LTz9WDKLMmbyrtq+V/5R1sYfYBQJPuVa3pJ1vX2I5M6XK n7TDu9gsW2v+zquOps/8aL/e/+R8ivEJomYzdnvH3EwfgT9WCOYJtlUL +sIq8eu45jXTVsFVLa0Fy5LKeFcfic+4C6AG676o5VSucVJLTWiftW47 RPA= + c23.idnes.cz. 251 IN A 185.17.119.54 +ENTRY_END + +SCENARIO_END diff -Nru knot-resolver-5.1.1/lib/cache/test.integr/deckard.yaml knot-resolver-5.2.1/lib/cache/test.integr/deckard.yaml --- knot-resolver-5.1.1/lib/cache/test.integr/deckard.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/cache/test.integr/deckard.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -3,8 +3,7 @@ - name: kresd binary: kresd additional: - - -f - - "1" + - --noninteractive templates: - lib/cache/test.integr/kresd_config.j2 - tests/integration/hints_zone.j2 diff -Nru knot-resolver-5.1.1/lib/cache/util.h knot-resolver-5.2.1/lib/cache/util.h --- knot-resolver-5.1.1/lib/cache/util.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/cache/util.h 2020-12-09 09:44:29.000000000 +0000 @@ -1,4 +1,4 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ -#include +#include uint32_t packet_ttl(const knot_pkt_t *pkt, bool is_negative); diff -Nru knot-resolver-5.1.1/lib/cookies/lru_cache.h knot-resolver-5.2.1/lib/cookies/lru_cache.h --- knot-resolver-5.1.1/lib/cookies/lru_cache.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/cookies/lru_cache.h 2020-12-09 09:44:29.000000000 +0000 @@ -7,13 +7,13 @@ #include #include -#if defined(ENABLE_COOKIES) +#if ENABLE_COOKIES #include #include #else #define KNOT_OPT_COOKIE_CLNT 8 #define KNOT_OPT_COOKIE_SRVR_MAX 32 -#endif /* defined(ENABLE_COOKIES) */ +#endif /* ENABLE_COOKIES */ #include "lib/defines.h" #include "lib/generic/lru.h" diff -Nru knot-resolver-5.1.1/lib/defines.h knot-resolver-5.2.1/lib/defines.h --- knot-resolver-5.1.1/lib/defines.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/defines.h 2020-12-09 09:44:29.000000000 +0000 @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2017 CZ.NIC, z.s.p.o. +/* Copyright (C) 2014-2020 CZ.NIC, z.s.p.o. * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -59,9 +59,10 @@ * Defines. */ #define KR_DNS_PORT 53 +#define KR_DNS_DOH_PORT 443 #define KR_DNS_TLS_PORT 853 #define KR_EDNS_VERSION 0 -#define KR_EDNS_PAYLOAD 4096 /* Default UDP payload (max unfragmented UDP is 1452B) */ +#define KR_EDNS_PAYLOAD 1232 /* Default UDP payload; see https://dnsflagday.net/2020/ */ #define KR_CACHE_DEFAULT_TTL_MIN (5) /* avoid bursts of queries */ #define KR_CACHE_DEFAULT_TTL_MAX (6 * 24 * 3600) /* 6 days, like the root NS TTL */ diff -Nru knot-resolver-5.1.1/lib/dnssec/signature.c knot-resolver-5.2.1/lib/dnssec/signature.c --- knot-resolver-5.1.1/lib/dnssec/signature.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/dnssec/signature.c 2020-12-09 09:44:29.000000000 +0000 @@ -282,7 +282,12 @@ goto fail; } - if (dnssec_sign_verify(sign_ctx, &signature) != 0) { + ret = dnssec_sign_verify(sign_ctx, + #if KNOT_VERSION_MAJOR >= 3 + false, + #endif + &signature); + if (ret != 0) { ret = kr_error(EBADMSG); goto fail; } diff -Nru knot-resolver-5.1.1/lib/dnssec/signature.h knot-resolver-5.2.1/lib/dnssec/signature.h --- knot-resolver-5.1.1/lib/dnssec/signature.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/dnssec/signature.h 2020-12-09 09:44:29.000000000 +0000 @@ -18,8 +18,7 @@ /** * Check the signature of the supplied RRSet. - * @param rrsigs RRSet containing signatures. - * @param pos Index of the signature record in the signature RRSet. + * @param rrsig RRSet containing signatures. * @param key Key to be used to validate the signature. * @param covered The covered RRSet. * @param trim_labels Number of the leftmost labels to be removed and replaced with '*.'. diff -Nru knot-resolver-5.1.1/lib/dnssec.c knot-resolver-5.2.1/lib/dnssec.c --- knot-resolver-5.1.1/lib/dnssec.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/dnssec.c 2020-12-09 09:44:29.000000000 +0000 @@ -193,14 +193,15 @@ } for (uint16_t i = 0; i < vctx->rrs->len; ++i) { - /* Consider every RRSIG that matches owner and covers the class/type. */ + /* Consider every RRSIG that matches and comes from the same query. */ const knot_rrset_t *rrsig = vctx->rrs->at[i]->rr; - if (rrsig->type != KNOT_RRTYPE_RRSIG) { + const bool ok = vctx->rrs->at[i]->qry_uid == vctx->qry_uid + && rrsig->type == KNOT_RRTYPE_RRSIG + && rrsig->rclass == covered->rclass + && knot_dname_is_equal(rrsig->owner, covered->owner); + if (!ok) continue; - } - if ((covered->rclass != rrsig->rclass) || !knot_dname_is_equal(covered->owner, rrsig->owner)) { - continue; - } + knot_rdata_t *rdata_j = rrsig->rrs.rdata; for (uint16_t j = 0; j < rrsig->rrs.count; ++j, rdata_j = knot_rdataset_next(rdata_j)) { int val_flgs = 0; diff -Nru knot-resolver-5.1.1/lib/generic/pack.h knot-resolver-5.2.1/lib/generic/pack.h --- knot-resolver-5.1.1/lib/generic/pack.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/generic/pack.h 2020-12-09 09:44:29.000000000 +0000 @@ -11,7 +11,7 @@ * backed by an array. * * @note Maximum object size is 2^16 bytes, see ::pack_objlen_t - * @TODO If some mistake happens somewhere, the access may end up in an infinite loop. + * @todo If some mistake happens somewhere, the access may end up in an infinite loop. * (equality comparison on pointers) * * # Example usage: diff -Nru knot-resolver-5.1.1/lib/generic/queue.c knot-resolver-5.2.1/lib/generic/queue.c --- knot-resolver-5.1.1/lib/generic/queue.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/generic/queue.c 2020-12-09 09:44:29.000000000 +0000 @@ -5,7 +5,7 @@ #include "lib/generic/queue.h" #include -KR_EXPORT void queue_init_impl(struct queue *q, size_t item_size) +void queue_init_impl(struct queue *q, size_t item_size) { q->len = 0; q->item_size = item_size; @@ -19,7 +19,7 @@ if (!q->chunk_cap) q->chunk_cap = 1; /* item_size big enough by itself */ } -KR_EXPORT void queue_deinit_impl(struct queue *q) +void queue_deinit_impl(struct queue *q) { assert(q); struct queue_chunk *p = q->head; @@ -46,7 +46,7 @@ } /* Return pointer to the space for the new element. */ -KR_EXPORT void * queue_push_impl(struct queue *q) +void * queue_push_impl(struct queue *q) { assert(q); struct queue_chunk *t = q->tail; // shorthand @@ -75,7 +75,7 @@ } /* Return pointer to the space for the new element. */ -KR_EXPORT void * queue_push_head_impl(struct queue *q) +void * queue_push_head_impl(struct queue *q) { /* When we have choice, we optimize for further _push_head, * i.e. when shifting or allocating a chunk, diff -Nru knot-resolver-5.1.1/lib/generic/queue.h knot-resolver-5.2.1/lib/generic/queue.h --- knot-resolver-5.1.1/lib/generic/queue.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/generic/queue.h 2020-12-09 09:44:29.000000000 +0000 @@ -8,7 +8,7 @@ * Both the head and tail of the queue can be accessed and pushed to, * but only the head can be popped from. * - * @note The implementation uses a singly linked list of blocks + * @note The implementation uses a singly linked list of blocks ("chunks") * where each block stores an array of values (for better efficiency). * * Example usage: @@ -142,25 +142,26 @@ struct queue; /* Non-inline functions are exported to be usable from daemon. */ -void queue_init_impl(struct queue *q, size_t item_size); -void queue_deinit_impl(struct queue *q); -void * queue_push_impl(struct queue *q); -void * queue_push_head_impl(struct queue *q); +KR_EXPORT void queue_init_impl(struct queue *q, size_t item_size); +KR_EXPORT void queue_deinit_impl(struct queue *q); +KR_EXPORT void * queue_push_impl(struct queue *q); +KR_EXPORT void * queue_push_head_impl(struct queue *q); struct queue_chunk; struct queue { - size_t len; - uint16_t chunk_cap, item_size; - struct queue_chunk *head, *tail; + size_t len; /**< the current number of items in queue */ + uint16_t chunk_cap; /**< max. number of items in each chunk */ + uint16_t item_size; /**< sizeof() each item */ + struct queue_chunk *head, *tail; /*< first and last chunk (or NULLs) */ }; struct queue_chunk { - struct queue_chunk *next; /*< head -> ... -> tail */ + struct queue_chunk *next; /*< *head -> ... -> *tail; each is non-empty */ int16_t begin, end, cap, pad_; /*< indices: zero is closest to head */ /*< We could fit into uint8_t for example, but the choice of (3+1)*2 bytes * is a compromise between wasting space and getting a good alignment. * In particular, queue_t(type*) will store the pointers on addresses - * aligned to the pointer size, in both 64-bit and 32-bit platforms. + * aligned to the pointer size, on both 64-bit and 32-bit platforms. */ char data[]; /**< The item data. We use "char" to satisfy the C99+ aliasing rules. @@ -175,9 +176,7 @@ { assert(q); struct queue_chunk *h = q->head; - if (unlikely(!h)) - return NULL; - assert(h->end > h->begin); + assert(h && h->end > h->begin); return h->data + h->begin * q->item_size; } @@ -185,9 +184,7 @@ { assert(q); struct queue_chunk *t = q->tail; - if (unlikely(!t)) - return NULL; - assert(t->end > t->begin); + assert(t && t->end > t->begin); return t->data + (t->end - 1) * q->item_size; } @@ -198,6 +195,13 @@ assert(h && h->end > h->begin); if (h->end - h->begin == 1) { /* removing the last element in the chunk */ + assert((q->len == 1) == (q->head == q->tail)); + if (q->len == 1) { + q->tail = NULL; + assert(!h->next); + } else { + assert(h->next); + } q->head = h->next; free(h); } else { diff -Nru knot-resolver-5.1.1/lib/generic/test_queue.c knot-resolver-5.2.1/lib/generic/test_queue.c --- knot-resolver-5.1.1/lib/generic/test_queue.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/generic/test_queue.c 2020-12-09 09:44:29.000000000 +0000 @@ -14,6 +14,12 @@ queue_int_t q; queue_init(q); + /* Case of emptying the queue (and using again) has been broken for a long time. */ + queue_push(q, 2); + queue_pop(q); + queue_push(q, 4); + queue_pop(q); + queue_push_head(q, 2); queue_push_head(q, 1); queue_push_head(q, 0); diff -Nru knot-resolver-5.1.1/lib/generic/trie.h knot-resolver-5.2.1/lib/generic/trie.h --- knot-resolver-5.1.1/lib/generic/trie.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/generic/trie.h 2020-12-09 09:44:29.000000000 +0000 @@ -19,8 +19,8 @@ * - key lengths are limited by 2^32-1 ATM * * XXX EDITORS: trie.{h,c} are synced from - * https://gitlab.labs.nic.cz/knot/knot-dns/tree/68352fc969/src/contrib/qp-trie - * only with tiny adjustments, mostly #includes and KR_EXPORT. + * https://gitlab.nic.cz/knot/knot-dns/tree/68352fc969/src/contrib/qp-trie + * only with tiny adjustments, mostly include lines and KR_EXPORT. */ /*! \brief Element value. */ diff -Nru knot-resolver-5.1.1/lib/generic/trie.spdx knot-resolver-5.2.1/lib/generic/trie.spdx --- knot-resolver-5.1.1/lib/generic/trie.spdx 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/generic/trie.spdx 2020-12-09 09:44:29.000000000 +0000 @@ -5,6 +5,6 @@ DocumentNamespace: http://spdx.org/spdxdocs/spdx-v2.1-f99c0e11-6afb-46ce-af96-0955a83957bb PackageName: knotdns-trie -PackageDownloadLocation: git+https://gitlab.labs.nic.cz/knot/knot-dns.git@68352fc969bc04aa4aa8203e113ce747d887f410#src/contrib/qp-trie/trie.c +PackageDownloadLocation: git+https://gitlab.nic.cz/knot/knot-dns.git@68352fc969bc04aa4aa8203e113ce747d887f410#src/contrib/qp-trie/trie.c PackageOriginator: Organization: Knot DNS contributors PackageLicenseDeclared: GPL-3.0-or-later diff -Nru knot-resolver-5.1.1/lib/layer/iterate.c knot-resolver-5.2.1/lib/layer/iterate.c --- knot-resolver-5.1.1/lib/layer/iterate.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/layer/iterate.c 2020-12-09 09:44:29.000000000 +0000 @@ -474,11 +474,15 @@ return result; } -static void finalize_answer(knot_pkt_t *pkt, struct kr_query *qry, struct kr_request *req) +static int finalize_answer(knot_pkt_t *pkt, struct kr_request *req) { /* Finalize header */ - knot_pkt_t *answer = req->answer; - knot_wire_set_rcode(answer->wire, knot_wire_get_rcode(pkt->wire)); + knot_pkt_t *answer = kr_request_ensure_answer(req); + if (answer) { + knot_wire_set_rcode(answer->wire, knot_wire_get_rcode(pkt->wire)); + req->state = KR_STATE_DONE; + } + return req->state; } static int unroll_cname(knot_pkt_t *pkt, struct kr_request *req, bool referral, const knot_dname_t **cname_ret) @@ -605,15 +609,17 @@ } /* The validator still can't handle multiple zones in one answer, * so we only follow if a single label is replaced. - * TODO: this still isn't 100%, as the target might have a NS+DS, - * possibly leading to a SERVFAIL for the in-bailiwick name. */ + * Forwarding appears to be even more sensitive to this. + * TODO: iteration can probably handle the remaining cases, + * but overall it would be better to have a smarter validator + * (and thus save roundtrips).*/ const int pending_labels = knot_dname_labels(pending_cname, NULL); if (pending_labels != cname_labels) { cname = pending_cname; break; } - if (knot_dname_matched_labels(pending_cname, cname) != - (cname_labels - 1)) { + if (knot_dname_matched_labels(pending_cname, cname) != cname_labels - 1 + || query->flags.FORWARD) { cname = pending_cname; break; } @@ -677,8 +683,7 @@ array->at[last_idx] = entry; entry->to_wire = true; } - finalize_answer(pkt, query, req); - return KR_STATE_DONE; + return finalize_answer(pkt, req); } return kr_ok(); } @@ -801,7 +806,7 @@ if (state != kr_ok()) { return KR_STATE_FAIL; } - finalize_answer(pkt, query, req); + return finalize_answer(pkt, req); } else { /* Answer for sub-query; DS, IP for NS etc. * It may contains NSEC \ NSEC3 records for @@ -864,8 +869,7 @@ return KR_STATE_FAIL; } - finalize_answer(pkt, query, req); - return KR_STATE_DONE; + return finalize_answer(pkt, req); } @@ -902,7 +906,9 @@ if (qry->sclass != KNOT_CLASS_IN || (knot_rrtype_is_metatype(qry->stype) /* && qry->stype != KNOT_RRTYPE_ANY hmm ANY seems broken ATM */)) { - knot_wire_set_rcode(ctx->req->answer->wire, KNOT_RCODE_NOTIMPL); + knot_pkt_t *ans = kr_request_ensure_answer(ctx->req); + if (!ans) return ctx->req->state; + knot_wire_set_rcode(ans->wire, KNOT_RCODE_NOTIMPL); return KR_STATE_FAIL; } @@ -986,36 +992,6 @@ #endif } -static int resolve_notimpl(knot_pkt_t *pkt, struct kr_request *req, struct kr_query *qry) -{ - if (qry->stype == KNOT_RRTYPE_RRSIG && qry->parent != NULL) { - /* RRSIG subquery have got NOTIMPL. - * Possible scenario - same NS is autoritative for child and parent, - * but child isn't signed. - * We got delegation to parent, - * then NS responded as NS for child zone. - * Answer contained record been requested, but no RRSIGs, - * Validator issued RRSIG query then. If qname is zone name, - * we can get NOTIMPL. Ask for DS to find out security status. - * TODO - maybe it would be better to do this in validator, when - * RRSIG revalidation occurs. - */ - struct kr_rplan *rplan = &req->rplan; - struct kr_query *next = kr_rplan_push(rplan, qry->parent, qry->sname, - qry->sclass, KNOT_RRTYPE_DS); - if (!next) { - return KR_STATE_FAIL; - } - kr_zonecut_set(&next->zone_cut, qry->parent->zone_cut.name); - kr_zonecut_copy(&next->zone_cut, &qry->parent->zone_cut); - kr_zonecut_copy_trust(&next->zone_cut, &qry->parent->zone_cut); - next->flags.DNSSEC_WANT = true; - qry->flags.RESOLVED = true; - return KR_STATE_DONE; - } - return resolve_badmsg(pkt, req, qry); -} - /** Resolve input query or continue resolution with followups. * * This roughly corresponds to RFC1034, 5.3.3 4a-d. @@ -1091,6 +1067,8 @@ case KNOT_RCODE_NXDOMAIN: break; /* OK */ case KNOT_RCODE_YXDOMAIN: /* Basically a successful answer; name just doesn't fit. */ + if (!kr_request_ensure_answer(req)) + return req->state; knot_wire_set_rcode(req->answer->wire, KNOT_RCODE_YXDOMAIN); break; case KNOT_RCODE_REFUSED: @@ -1101,11 +1079,9 @@ } /* fall through */ case KNOT_RCODE_FORMERR: - VERBOSE_MSG("<= rcode: %s\n", rcode ? rcode->name : "??"); - return resolve_badmsg(pkt, req, query); case KNOT_RCODE_NOTIMPL: VERBOSE_MSG("<= rcode: %s\n", rcode ? rcode->name : "??"); - return resolve_notimpl(pkt, req, query); + return resolve_badmsg(pkt, req, query); default: VERBOSE_MSG("<= rcode: %s\n", rcode ? rcode->name : "??"); return resolve_error(pkt, req); @@ -1134,7 +1110,8 @@ } rrarray_finalize: - /* Finish construction of libknot-format RRsets. */ + /* Finish construction of libknot-format RRsets. + * We do this even if dropping the answer, though it's probably useless. */ (void)0; ranked_rr_array_t *selected[] = kr_request_selected(req); for (knot_section_t i = KNOT_ANSWER; i <= KNOT_ADDITIONAL; ++i) { diff -Nru knot-resolver-5.1.1/lib/layer/iterate.h knot-resolver-5.2.1/lib/layer/iterate.h --- knot-resolver-5.1.1/lib/layer/iterate.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/layer/iterate.h 2020-12-09 09:44:29.000000000 +0000 @@ -17,6 +17,7 @@ }; /** Classify response by type. */ +KR_EXPORT int kr_response_classify(const knot_pkt_t *pkt); /** Make next iterative query. */ diff -Nru knot-resolver-5.1.1/lib/layer/test.integr/deckard.yaml knot-resolver-5.2.1/lib/layer/test.integr/deckard.yaml --- knot-resolver-5.1.1/lib/layer/test.integr/deckard.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/layer/test.integr/deckard.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -3,8 +3,7 @@ - name: kresd binary: kresd additional: - - -f - - "1" + - --noninteractive templates: - lib/layer/test.integr/kresd_config.j2 - tests/integration/hints_zone.j2 diff -Nru knot-resolver-5.1.1/lib/layer/validate.c knot-resolver-5.2.1/lib/layer/validate.c --- knot-resolver-5.1.1/lib/layer/validate.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/layer/validate.c 2020-12-09 09:44:29.000000000 +0000 @@ -481,7 +481,6 @@ VERBOSE_MSG(qry, "<= bogus proof of DS non-existence\n"); qry->flags.DNSSEC_BOGUS = true; } else if (proved_name[0] != '\0') { /* don't go to insecure for . DS */ - VERBOSE_MSG(qry, "<= DS doesn't exist, going insecure\n"); qry->flags.DNSSEC_NODS = true; /* Rank the corresponding nonauth NS as insecure. */ for (int i = 0; i < req->auth_selected.len; ++i) { @@ -516,7 +515,7 @@ return ret; } -static const knot_dname_t *find_first_signer(ranked_rr_array_t *arr) +static const knot_dname_t *find_first_signer(ranked_rr_array_t *arr, struct kr_query *qry) { for (size_t i = 0; i < arr->len; ++i) { ranked_rr_array_entry_t *entry = arr->at[i]; @@ -527,8 +526,16 @@ !kr_rank_test(entry->rank, KR_RANK_MISMATCH))) { continue; } - if (rr->type == KNOT_RRTYPE_RRSIG) { - return knot_rrsig_signer_name(rr->rrs.rdata); + if (rr->type != KNOT_RRTYPE_RRSIG) { + continue; + } + const knot_dname_t *signame = knot_rrsig_signer_name(rr->rrs.rdata); + if (knot_dname_in_bailiwick(rr->owner, signame) >= 0) { + return signame; + } else { + /* otherwise it's some nonsense, so we skip it */ + kr_log_q(qry, "vldr", "protocol violation: " + "out-of-bailwick RRSIG signer, skipping\n"); } } return NULL; @@ -536,64 +543,55 @@ static const knot_dname_t *signature_authority(struct kr_request *req) { - const knot_dname_t *signer_name = find_first_signer(&req->answ_selected); + const knot_dname_t *signer_name = find_first_signer(&req->answ_selected, req->current_query); if (!signer_name) { - signer_name = find_first_signer(&req->auth_selected); + signer_name = find_first_signer(&req->auth_selected, req->current_query); } return signer_name; } -static int rrsig_not_found(kr_layer_t *ctx, const knot_rrset_t *rr) +static int rrsig_not_found(const kr_layer_t * const ctx, const knot_pkt_t * const pkt, + const knot_rrset_t * const rr) { - struct kr_request *req = ctx->req; - struct kr_query *qry = req->current_query; - - /* Parent-side record, so don't ask for RRSIG. - * We won't receive it anyway. */ - if (qry->stype == KNOT_RRTYPE_DS) { + /* Signatures are missing. There might be a zone cut that we've skipped + * and transitions to insecure. That can commonly happen when iterating + * and both sides of that cut are served by the same IP address(es). + * We'll try proving that the name truly is insecure - by spawning + * a DS sub-query on a suitable QNAME. + */ + struct kr_request * const req = ctx->req; + struct kr_query * const qry = req->current_query; + + if (qry->flags.FORWARD || qry->flags.STUB) { + /* Undiscovered signed cuts can't happen in the current forwarding + * algorithm, so this function shouldn't be able to help. */ return KR_STATE_FAIL; } - struct kr_zonecut *cut = &qry->zone_cut; - const knot_dname_t *cut_name_start = qry->zone_cut.name; - bool use_cut = true; - if (knot_dname_in_bailiwick(rr->owner, cut_name_start) < 0) { - int zone_labels = knot_dname_labels(qry->zone_cut.name, NULL); - int matched_labels = knot_dname_matched_labels(qry->zone_cut.name, rr->owner); - int skip_labels = zone_labels - matched_labels; - while (skip_labels--) { - cut_name_start = knot_wire_next_label(cut_name_start, NULL); - } - /* try to find the name wanted among ancestors */ - use_cut = false; - while (cut->parent) { - cut = cut->parent; - if (knot_dname_is_equal(cut_name_start, cut->name)) { - use_cut = true; - break; - } - }; - } - struct kr_rplan *rplan = &req->rplan; - struct kr_query *next = kr_rplan_push(rplan, qry, rr->owner, rr->rclass, KNOT_RRTYPE_RRSIG); + /* Find cut_next: the name at which to try finding the "missing" zone cut. */ + const knot_dname_t * const cut_top = qry->zone_cut.name; + const int next_depth = knot_dname_in_bailiwick(rr->owner, cut_top); + if (next_depth <= 0) { + return KR_STATE_FAIL; // shouldn't happen, I think + } + /* Add one extra label to cur_top, i.e. descend one level below current zone cut */ + const knot_dname_t * const cut_next = rr->owner + + knot_dname_prefixlen(rr->owner, next_depth - 1, NULL); + + /* Spawn that DS sub-query. */ + struct kr_query * const next = kr_rplan_push(&req->rplan, qry, cut_next, + rr->rclass, KNOT_RRTYPE_DS); if (!next) { return KR_STATE_FAIL; } - kr_zonecut_init(&next->zone_cut, cut_name_start, &req->pool); - if (use_cut) { - kr_zonecut_copy(&next->zone_cut, cut); - kr_zonecut_copy_trust(&next->zone_cut, cut); - } else { - next->flags.AWAIT_CUT = true; - } - if (qry->flags.FORWARD) { - next->flags.AWAIT_CUT = false; - } + kr_zonecut_init(&next->zone_cut, qry->zone_cut.name, &req->pool); + kr_zonecut_copy(&next->zone_cut, &qry->zone_cut); + kr_zonecut_copy_trust(&next->zone_cut, &qry->zone_cut); next->flags.DNSSEC_WANT = true; return KR_STATE_YIELD; } -static int check_validation_result(kr_layer_t *ctx, ranked_rr_array_t *arr) +static int check_validation_result(kr_layer_t *ctx, const knot_pkt_t *pkt, ranked_rr_array_t *arr) { int ret = KR_STATE_DONE; struct kr_request *req = ctx->req; @@ -646,7 +644,7 @@ VERBOSE_MSG(qry, ">< cut changed (new signer), needs revalidation\n"); ret = KR_STATE_YIELD; } else if (kr_rank_test(invalid_entry->rank, KR_RANK_MISSING)) { - ret = rrsig_not_found(ctx, rr); + ret = rrsig_not_found(ctx, pkt, rr); } else if (!kr_rank_test(invalid_entry->rank, KR_RANK_SECURE)) { qry->flags.DNSSEC_BOGUS = true; ret = KR_STATE_FAIL; @@ -1110,13 +1108,13 @@ } /* check validation state and spawn subrequests */ if (!req->answ_validated) { - ret = check_validation_result(ctx, &req->answ_selected); + ret = check_validation_result(ctx, pkt, &req->answ_selected); if (ret != KR_STATE_DONE) { return ret; } } if (!req->auth_validated) { - ret = check_validation_result(ctx, &req->auth_selected); + ret = check_validation_result(ctx, pkt, &req->auth_selected); if (ret != KR_STATE_DONE) { return ret; } diff -Nru knot-resolver-5.1.1/lib/layer/validate.test.integr/deckard.yaml knot-resolver-5.2.1/lib/layer/validate.test.integr/deckard.yaml --- knot-resolver-5.1.1/lib/layer/validate.test.integr/deckard.yaml 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/lib/layer/validate.test.integr/deckard.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +programs: +- name: kresd + binary: kresd + additional: + - -n + templates: + - lib/layer/validate.test.integr/kresd_config.j2 + configs: + - config diff -Nru knot-resolver-5.1.1/lib/layer/validate.test.integr/fwd_insecure_but_rrsig_signer_invalid.rpl knot-resolver-5.2.1/lib/layer/validate.test.integr/fwd_insecure_but_rrsig_signer_invalid.rpl --- knot-resolver-5.1.1/lib/layer/validate.test.integr/fwd_insecure_but_rrsig_signer_invalid.rpl 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/lib/layer/validate.test.integr/fwd_insecure_but_rrsig_signer_invalid.rpl 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,294 @@ +; SPDX-License-Identifier: GPL-3.0-or-later + val-override-date: "20200722144207" + trust-anchor: ". IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D" +CONFIG_END + +SCENARIO_BEGIN Forwarding: forwarder sent RRSIGs with invalid signed for an insecure zone, it should not cause SERVFAIL because the zone is insecure + + +; forwarder +RANGE_BEGIN 0 100 + ADDRESS 8.8.8.8 + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA AD CD NOERROR +SECTION QUESTION +. IN DNSKEY +SECTION ANSWER +. 86913 IN DNSKEY 256 3 8 AwEAAdauOGxLhfAKFTTZwGhBXbk793QK dWIQRjiSftWdusCwkPhNyJrIjwtNffCW XGLlZAbpcs414RE3oS1qVwV+AdXsO92S Bu5haGlxMUk0NqZO7Xlf84/wrzGZVRRo uPo5pNX/CKS8Mv9UOi0olKGCu31dNfh8 qCszWZcloLDgeLzSnQSkvFoGe69vNCfh 7feESKedkBC2qRz0BZv9+oJI0IY/3D7W EnV0NOlf8gSHozhfJFJ/ZAKtvw/Q3ogr VJFk0LyVaU/NVtVA5FM4pVMIRID7pfrP i78aAzG7b/Wh/Pce4jPAIpS3dApq25Yk vMuPvfB91NMf9FemKwlp78PBVcM= +. 86913 IN DNSKEY 257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexT BAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq 7HrxRixHlFlExOLAJr5emLvN7SWXgnLh 4+B5xQlNVz8Og8kvArMtNROxVQuCaSnI DdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLr jyBxWezF0jLHwVN8efS3rCj/EWgvIWgb 9tarpVUDK/b58Da+sqqls3eNbuv7pr+e oZG+SrDK6nWeL3c6H5Apxz7LjVc1uTId sIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6 +cn8HFRm+2hM8AnXGXws9555KrUB5qih ylGa8subX2Nn6UwNR1AkUTV74bU= +. 86913 IN RRSIG DNSKEY 8 0 172800 20200811000000 20200721000000 20326 . IcMH/yNRoNKkCPmOo8MDcMEZO4sF8p0A 8xgASRnD1c0t+VSU5NRzh05eME7RJrRP 31T/E4eUh+jyI18Gz/O5Lg02Zu1wmcOy Mnkr+bfU+Al7pCztj+6aGTUl34HFyWtM cChKkeJQDeJoBtyVDVa4oL1FQs4Ml6HC OOjzoOKIHakrfCLyaktN82G+uNFXt0CB SGR2xQSWDKnzqSJqCep9X8NtNjjAFus2 g8weAXomG2+gRlrNfQAFqcGPjLHeVtZv yco3u0u8ZOp+8PC8fnlLhtpJ3DBXgwFp wY3V7uM7Zfabcio64st79wu4zNwZR5uf IEpKciMtUh8J8LfVWFM62w== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA AD CD NXDOMAIN +SECTION QUESTION +_TA-4f66. IN NULL +SECTION AUTHORITY +. 86161 IN NSEC aaa. NS SOA RRSIG NSEC DNSKEY +. 86161 IN RRSIG NSEC 8 0 86400 20200804050000 20200722040000 46594 . zNoDySatAPoT5YV6XmQcz3qs60+GxuEo yITrzrWMEU1keQNU+6663BM6N/F6G2nd b5Bj5cY5m7MN7iZnBFN/cXQd7EWjdLTC Fw7FxBPc3J51vqShIaM2xxpm9RRdpaB/ vfei+x2e+4YRCJJdw81qmrUBBohANyNW 119JAEufoTgAVY9jPd8699lJ4svMY10E avFAL+PTfC/la9KKNPlOgDgbVRjpUZcU wLeooyqggterm4kXcnWahk3PtG3tmB7A zz1qccY6rOeQALloUQ3Im1Wl9s6pbExZ XI/6qBXYetdexB6DpebnsAk1P4wTprhQ iIxvZpHCppz/jMAx/KDRjQ== +. 86161 IN RRSIG SOA 8 0 86400 20200804050000 20200722040000 46594 . qI98OcNtjSzHbTbiNg6MZRopwcTAW0Cm JiHKdcEOzGY+Tabxyl76YDeVWEZuKrYG pzeqFLKC05W+4nQrUKTmCoI89YHFNdAc f8uO7zbSEM6dFlS4ksCZFkZZHb2hjp4g KI1MEkI56EsojYqEDa1fXakpytEucvKO 2qJgcqPVkb29lk2lePIicO4YIddI38M4 BNdKsCnEhdMYC76lKls7EYMfJeHAr+FX c3SZIAG/r3ov2KR0u/CNqDDlKcfsGCVt wRCWE/EYUMuZbCNW7/cZ8fCWps1Xn7fu I8N7YLgMTqAsAmXx4I86zf8TAUTslgYN 5IR69n16/l2h6QP9vJPS3g== +. 86161 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2020072200 1800 900 604800 86400 +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA AD CD NOERROR +SECTION QUESTION +fi. IN DS +SECTION ANSWER +fi. 48962 IN DS 44855 8 2 80cd184a31c50d5dc44b4f98811f298315986b44edcc0666455c6aef7dbd997e +fi. 48962 IN RRSIG DS 8 1 86400 20200803200000 20200721190000 46594 . HhKwynZK3BYIdOYlwFp6wLacu8uci+ig va3NggxT/LdJqCXlIpuBV5EBGFnEowA9 dYJMdJkkbizYmqkTLbAqaomS5nH3Juz4 5rvjtqi2/viyZjy9VfIPtWx5T5+xfSHH tLac4rJk8ieKSxhpjcc9tITGFT0cnU7U kpWD8OO/0toJF94diLleqS7M/uCDIYyj zgvwIuvx0WxwgxG6bG45EvwfWQbIMRTi R2XSzKtOTRSBbXtI0XKoGrTblOkVxCei uQka3Jqpm/HvmFa9rsspylXTC/6VkKwC u+/TogoJ9vobAr9vXpLx0LiYTkh3GKzZ QL0uw/36jcqplPHlddICPg== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA AD CD NOERROR +SECTION QUESTION +fi. IN DNSKEY +SECTION ANSWER +fi. 793 IN DNSKEY 256 3 8 AwEAAbfXUBTwb2oWNVvv3kCwgaER6vZ6 76rm5DsLmQjOuClDEHXRtH4JmX+nbjmX yFfeNRBasI0jr2sLIYSPB2TF7ctF/FLB a4zgEKIOTiwM2Q58bBxzUhXtU2KAgqEo V5X7kHYau899uJSW+CUJhKAPYpb/sb41 VqSUrsxkACf5ocZ4k4K57H81B2OKZSHC u6C6h4h7D59l8fwYg7F+RAr2vJENfbSM EjHfIWt54x7WfDuCKGDYvpoKmdgg/ACg 6CyLMttbml5dCZ9XJyWyFbs7eJZYGOgN Uw/3ezxmouChVSqqOeVzZAwfsUAsjb1l 1GSSj3YSiRVY4FWewrdw7XL09v0= +fi. 793 IN DNSKEY 257 3 8 AwEAAb9AMR4NV2YxZH9E6ELMFY5DOszk dTd5AxhSg1YZWi8B9cruHXjghFCmApd1 VfUyQ4MX3DZbskfML/ToxumeSv9OhfA4 I6Fao9bN9UxsBbFlkqwqhAGmuJapSgNu yMWArSoUG4XB/dykPNdyFt+3t1dEH/S3 hS5JmZccSI6YAjnUfG+Pd45R8ljO8ERI 1wSa0IJjDArkuFaLcGrtjR9GJluVmM15 0gWVUIiUkBfDUz8pFjqAWewk0QY9TX22 Z8gfl3yKhO9TYPVHN1oMHTydVqjQKbdb s/BvGXx/GPh63OOxE2ICmxXcz2Ma+082 eA0DfwKLo86PFOre8YK7pEWuw4U= +fi. 793 IN RRSIG DNSKEY 8 1 900 20200805163544 20200722030600 44855 fi. p9isNtIsiD4yhCQkiVeyfPva4iXV8+oE hhU/SEec2ykGjs22yiscFKppV/4s738y Tl58F682IXAq5jolf/6ShAMOUqxzMD1j 5nphLZ62O5B/r6Ah9JiJf9l748mXHCAW kOnwep4IpkEJkiS1lOHgcxd0hw/rsRNZ QAAgiDhYBGcTIWLw6FFsWL/sMJTEnMim 4QzkDLrF4MlFRduQSffC2N7mBbtQpwYt BFS38tZHQZ7w48or3GGJw7ejwR7IQQQJ VYO7yWr13eLZiGPnne51tvltO7LVeDbl cskxbGWYdtTaB+klcukrVlApqUopqjbT mgzA60/IMKe4PXk+QnzZeA== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +dIGiTrAnSIT.FI. IN DS +SECTION AUTHORITY +FI. 1334 IN SOA a.FI. fi-domain-tech.ficora.FI. 2020072239 3605 1800 2419200 86400 +FI. 21134 IN RRSIG SOA 8 1 86400 20200805082122 20200722074635 20436 fi. mXyNMPJRTLMLOKDjZ2ET/z1SOO9g0a8o o1dsHchts2X0uPvYu6Hc8quBCmUbmjhe qu9dRDiDtjCyddFScwn9FTI9CRMJKytB 1LVcrlBrbuWFlmDilRLkhrK2gmm6MOYU /VGgj+kgEtIQE7NSGF35NQHTJfDw1Jmo FxGAo7I//QBAHJemx5Q4YmbVMDiyTBjz FCLSdAjCGouJTRU9jFgweoEivGk+yD+H vsaaG7NTumzK4miU3SL4sMPb9NYr+HIR 2j+6pUjD05x9UOKAfIQzFnRcToDB1KUo ZZr8teWnLH3fbHCq9JGfz338Eju0wdfe 0E2CGL6Cc77+VThotyTrAg== +es7p5jquq6ng6hd9vn5q3el38s2vuoqh.FI. 21134 IN NSEC3 1 1 5 7298d6895c6c0415 escp8o5mqifge1u5itme7ju27k5hf19a NS DS RRSIG +es7p5jquq6ng6hd9vn5q3el38s2vuoqh.FI. 21134 IN RRSIG NSEC3 8 2 86400 20200805124944 20200722030600 20436 fi. S/DGd4jI4zndv50oMGp2BmB/aH9/M3AX /My9hwZ4zi2r88DrYiNyd2ghyuUvZO9a lvY2NcB9cX9sjAdQAM/xmoHsoraB5YWV v53YBTJDF+kY7BzO5mqImNWmkpe/Kcxt Mpp9Gz02ySpPp37dot4FbdK9A0RWERVw XkoEaFvLkHRf3QGMJUBnCjKJ7r448axs OAKdHIIQb6aG6OATML6mJx6xHOeI8CFl giTrgixGOR/qGP92i9ErcP9iQ2lvHlfK A7SuOLi6uSPBYWIgZzkqGJOpJ0o+cPHV 69QX/SP7m0qr00shV+rxfyppHCLUpKBx iXacRghILlVV5mUgV/25lw== +ml0llbvj7rbbtgbp31q2j3d3qv89643r.FI. 21134 IN NSEC3 1 1 5 7298d6895c6c0415 ml0mjk4qs3b070682obd52l1p9v07cl6 NS SOA TXT RRSIG DNSKEY NSEC3PARAM +ml0llbvj7rbbtgbp31q2j3d3qv89643r.FI. 21134 IN RRSIG NSEC3 8 2 86400 20200804210456 20200722030600 20436 fi. bLLvaTmn2WXk8RxKz7Kb28FoDSgNuIoj ZPrpnwyYVRltfYvOe8wOtzzPQtDYj9F8 bqZgPmZFIAQfsKJk86NMsEWvArGYhX8z 1w6z4qkfpgXpLGeV6fNjLMi/YHZRHQJn cELLNcxh/U9e+xuURCr55XzgzpVVnXpk fm2848LbLO/9p2ZltOGd+GWcyQxxt/aK FlSHUHz2Zp27/9wNCxRyQ8EKtR4eIic8 T9p4kgu5w6302GPSAlfbFCX8Yf+ikMvE Fy7XdbLiE+Uyt0PjEnayT51kqxL2DJFe vtY6Y+MSKazC5xBJudtB6S3owmCDd4PL OhOtxu8lwuuU/FVLteZwvg== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +diGITrANsiT.fI. IN NS +SECTION ANSWER +diGITrANsiT.fI. 3134 IN NS dns1.ficora.fI. +diGITrANsiT.fI. 3134 IN NS dns2.ficora.fI. +diGITrANsiT.fI. 3134 IN NS ns-secondary.funet.fI. +diGITrANsiT.fI. 3134 IN NS ns1.z.fI. +diGITrANsiT.fI. 3134 IN NS ns2.z.fI. +; following RRSIG signer is out-of-bailiwick, i.e. nonsense +diGITrANsiT.fI. 3134 IN RRSIG NS 10 2 3600 20190517083644 20190117083646 28100 droneinfo.fi. XFNrVGseG3exPEFC58o3tgRckNghA9+G Psc8w8lUgJW7NGPuWHM4aSuhgMWL3nxk kCqTYI60RerzNV1PNxFLlfyBzuwi6rqe dOjpud7Nr9giKQc2r2YsOSLrSfcRN4Wq KGllqTVXZAYIbF5+QYA+2x3sY1StATQS mb2qqYPPB6iR/EPuHOn/1DA9gzJKXdQS wWwRWSuzBtO89q/e/zhSlCsWhk96POR7 du1KpJb58wPdNm6+Jznwj7E7KphZaTID mexL5S9Sf6VwDNDHkSOGT9x182tjIXTs kZcpFZgJTDgrR+vwVwjXmvAs/CKQmMax D0ZuRbMjvgvaMkCsBRjtDQ== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +Api.dIGiTRaNsit.Fi. IN A +SECTION ANSWER +Api.dIGiTRaNsit.Fi. 3357 IN CNAME digitransit-prod.trafficmanager.net. +digitransit-aks.westeurope.cloudapp.azure.com. 9 IN A 40.119.148.209 +digitransit-prod.trafficmanager.net. 299 IN CNAME digitransit-aks.westeurope.cloudapp.azure.com. +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA AD CD NOERROR +SECTION QUESTION +nET. IN DS +SECTION ANSWER +nET. 56393 IN DS 35886 8 2 7862b27f5f516ebe19680444d4ce5e762981931842c465f00236401d8bd973ee +nET. 56393 IN RRSIG DS 8 1 86400 20200803200000 20200721190000 46594 . PtZ4PuUSHtwvVksquoCtgL5ylNkYaBJk uXVY0Xx4FOyJ8U5kJwlQzScXS8/W7/4m NMLRJWvDIfEMTwpRtFpgd71THg5w3M+O He4GoQ5dGaaSuREvpYCHY+O6aeO/t3DX P3mTcps+CJlIOJckiRirvv3V1u7jmTGB t4jZ6Gn27CX9lPXGUkhWrDx9EOW1p7ky ZYtFGtkVxGmqnMqoNMSz+JaFmN43uaJU grJt6B8aKFIw3MR1Z4xCX3oYzd5jDdQt sS1h8frflyyN/dF/aIl5BW58sc8qkgGS kv8iUgHW1/E24chcMQFngnOuAAF6hjaA K4SJCdrCXUNiduL8H9Q5MQ== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA AD CD NOERROR +SECTION QUESTION +nEt. IN DNSKEY +SECTION ANSWER +nEt. 21146 IN DNSKEY 256 3 8 AQPeYYme8NvhAl+0XjyGqHVep4Y1T2Or RmO+L3QGULBlOe571PnxI+gRyXCQmtN7 WpoJxzALFSVBPsggqwOP+wnmCx8DZ49N HrfS7WbMtoYHTtiaIvHTjZZ88leuCtNL qfIH8N1Ax68Xnf4uKobYFgZXj0M2Zi7Y I84iFkCpIyZk6VIiJpvpNgyCK5mWetPF 2zmO2jXC8M045JIPam38reXD +nEt. 21146 IN DNSKEY 257 3 8 AQOYBnzqWXIEj6mlgXg4LWC0HP2n8eK8 XqgHlmJ/69iuIHsa1TrHDG6TcOra/pye GKwH0nKZhTmXSuUFGh9BCNiwVDuyyb6O BGy2Nte9Kr8NwWg4q+zhSoOf4D+gC9dE zg0yFdwT0DKEvmNPt0K4jbQDS4Yimb+u PKuF6yieWWrPYYCrv8C9KC8JMze2uT6N uWBfsl2fDUoV4l65qMww06D7n+p7Rbdw WkAZ0fA63mXVXBZF6kpDtsYD7SUB9jhh fLQE/r85bvg3FaSs5Wi2BaqN06SzGWI1 DHu7axthIOeHwg00zxlhTpoYCH0ldoQz +S65zWYi/fRJiyLSBb6JZOvn +nEt. 21146 IN RRSIG DNSKEY 8 1 86400 20200728162830 20200713162330 35886 net. BsxHqTUrVNqYdQdv5uriiUd/p+Dh5F12 /01oniA3F1keMZU1V+pbELcih+1gfLs3 i+f88p/9r3kM8gghxQtInzyJl3lPdeBM 7LjWuonQR5CzvfnM4WAgkVZZxmFB7l6b bj+ey9mwocAMR1ht9502MgB4eQLOHVve 3mdCXYuilxwQ9vOrVsFDLiELVoCDVtQi csatJy3Z/LU31IWtR8c6Ta/ItApgqsWg fU8Br3uyHesiDbA2FSfBb9qWFfGcNrDJ ZGF9dLCxvMeHSU5AKpEwX8flOKWKRVwM nB/+LB1owMmcyao82yJlL7y7vxfrQDb0 V0uJaQMEiQl7XGBsT+8Hgg== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +TRAFficManaGer.NEt. IN DS +SECTION AUTHORITY +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.NEt. 21540 IN NSEC3 1 1 0 - a1ruuffjkct2q54p78f8ejgj8jbk7i8b NS SOA RRSIG DNSKEY NSEC3PARAM +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.NEt. 21540 IN RRSIG NSEC3 8 2 86400 20200728064417 20200721053417 56519 net. B4N1ZLUcMCAkfgGNrmJfXylkUAbPnqxO 31ZQ5ZwU0AecyChOkGGTelH/87eM7RtG M7mqrM6zU9CnGwsgheqg2HCbpX7n5Fvl 1AclnJZBuFlF0hvejSO99rrLrLRaTWQt LyJwVQJWkMfgztCO5Mh2ngJfK6ZB1PLS xOFqVz0j5MTYYp9QwdL1PvLIaBAw0kTg cm3o476wB0glMo6yzDbK2A== +CS431SS8CTI7M6JHMN592GRLI9T17OK7.NEt. 21540 IN NSEC3 1 1 0 - cs49egn3atpo6m7fblhased3j00k92nb NS DS RRSIG +CS431SS8CTI7M6JHMN592GRLI9T17OK7.NEt. 21540 IN RRSIG NSEC3 8 2 86400 20200727063544 20200720052544 56519 net. FB73aPYNlR3FqN1gmxYeIccL+ybiv+8A ymQkJDevhRITdI76YqbobqbvAKQg9Knm lSe5OtWZEmI6h+qbYXtXfnAGl+GayzbL LsSyUABJdWp8ZuGooatayzGqjWUpIGZr qgAZIK+twkCKUicgi2XhCztnVr1wVf2z L0tRuctiZQRdoDlcRfFzXBsg21Kn3eVy ivjnZUzp93vL1ZrBhhZfMg== +NEt. 840 IN RRSIG SOA 8 1 900 20200729095953 20200722084953 56519 net. GEY1baT1zShB6uxFAyHlg6EPfEbCxp3E 3C2l8OeVCBwgzBdP9TwDeXH+//RcfT9w Q++Wan2q/0W5I9fJRWZKGDPkdmhOcvh9 VjJRlb3DwZdJEeDc3Rj3dip3MvL7OyZt 8lfv42/WoBYHXeduQwGknz0KbNpeSqU0 BjV4C9O/w5rUSjvk3z6Qka7jz3/0sz7H 4FfXqv5CxkUCKYyl9PzxuA== +NEt. 840 IN SOA a.gtld-servers.NEt. nstld.verisign-grs.com. 1595411993 1800 900 604800 86400 +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +TrAfFicMANAger.net. IN NS +SECTION ANSWER +TrAfFicMANAger.net. 21041 IN NS tm1.dns-tm.com. +TrAfFicMANAger.net. 21041 IN NS tm1.edgedns-tm.info. +TrAfFicMANAger.net. 21041 IN NS tm2.dns-tm.com. +TrAfFicMANAger.net. 21041 IN NS tm2.edgedns-tm.info. +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +Api.dIGiTRaNsit.Fi. IN A +SECTION ANSWER +Api.dIGiTRaNsit.Fi. 3585 IN CNAME digitransit-prod.trafficmanager.net. +digitransit-aks.westeurope.cloudapp.azure.com. 9 IN A 40.119.148.209 +digitransit-prod.trafficmanager.net. 285 IN CNAME digitransit-aks.westeurope.cloudapp.azure.com. +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +DigiTranSiT-pROd.tRaffiCmanAgeR.Net. IN A +SECTION ANSWER +DigiTranSiT-pROd.tRaffiCmanAgeR.Net. 299 IN CNAME digitransit-aks.westeurope.cloudapp.azure.com. +digitransit-aks.westeurope.cloudapp.azure.com. 9 IN A 40.119.148.209 +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +DigiTranSiT-pROd.tRaffiCmanAgeR.Net. IN A +SECTION ANSWER +DigiTranSiT-pROd.tRaffiCmanAgeR.Net. 299 IN CNAME digitransit-aks.westeurope.cloudapp.azure.com. +digitransit-aks.westeurope.cloudapp.azure.com. 9 IN A 40.119.148.209 +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA AD CD NOERROR +SECTION QUESTION +coM. IN DS +SECTION ANSWER +coM. 86251 IN DS 30909 8 2 e2d3c916f6deeac73294e8268fb5885044a833fc5459588f4a9184cfc41a5766 +coM. 86251 IN RRSIG DS 8 1 86400 20200803200000 20200721190000 46594 . F3sngBM8xYQ10Z3iIVWUqlMFLvecizXH 2d5kM4iguQL088Cv6xz1Ep3d4wUVEzlL YBBrsCQB627WztctcVPFiXUn22cWwzky 7yxjII8YY72V2x2/758hmZMCSHdSzJph By9Wv5Av5O8qqLt1QyYq8r6cZK7352Vk ICENa/OhKWX3dUvQ+EQe+3JUN5q/Xfeb WnCiuG2LgaWIgl/f+yjpMEkg808EUCCz 41Dbe81gwIadN+vvpjvpb3j5cBfozwqk thyCWlXt2+ZUUH4aSr5q5WsS8nD4gb70 6YC+A08woNmeYms8t40irruVMdlUzrXC Fz7+azz0zCEAc046A9v5fQ== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA AD CD NOERROR +SECTION QUESTION +coM. IN DNSKEY +SECTION ANSWER +coM. 20944 IN DNSKEY 256 3 8 AwEAAdVECVAFFKnwlH7lDYpsSvv50Z7E JP518luWdiN7X5igYJo6dLij/noOhYO0 ppmTghphtSqHn75/qMmETK9NiUfLW4M9 X8j/IvIr1xrTPEb6+dipDE9xKjhMGFUu fOeXHiBoMQiKLNzlssYuz90oQrEwCKpa 5R4cYYFiZaoeezi2NQeIAY82dh/8auvF zqCOewWx/J2zVh8YHqfkGeXyzsM= +coM. 20944 IN DNSKEY 257 3 8 AQPDzldNmMvZFX4NcNJ0uEnKDg7tmv/F 3MyQR0lpBmVcNcsIszxNFxsBfKNW9JYC Yqpik8366LE7VbIcNRzfp2h9OO8HRl+H +E08zauK8k7evWEmu/6od+2boggPoiEf GNyvNPaSI7FOIroDsnw/taggzHRX1Z7S OiOiPWPNIwSUyWOZ79VmcQ1GLkC6NlYv G3HwYmynQv6oFwGv/KELSw7ZSdrbTQ0H XvZbqMUI7BaMskmvgm1G7oKZ1YiF7O9i oVNc0+7ASbqmZN7Z98EGU/Qh2K/BgUe8 Hs0XVcdPKrtyYnoQHd2ynKPcMMlTEih2 /2HDHjRPJ2aywIpKNnv4oPo/ +coM. 20944 IN RRSIG DNSKEY 8 1 86400 20200729182421 20200714181921 30909 com. kWt6r1qzHb1LABToPGz61aVgRBHMIkS+ x1FbuHxh+Ha9nYtlnl1AOju4CjMC6gje qBXYhhwpZWC4VFSE+hVXuI2NNEPvtcD1 Om9eu69KEK8d1rXmho+PBzJyzXSSUpM6 KtapTL0NDdjg+uCt6YmWNli/e3QdRAoI u5eNnmFBK5viaGcnIP5l8/QdXH+dBmfi qrrs+z0oJv89euCCjH0UeMfVJUetHTox MLiB4GlMyQnPWsNXNZPzQEWk8CeLEhVu e8QVvMQmq+GfcvRF/jFnTsL9ILTMYiJR 8gYp5YnFtBgtrqPoekmAaJ0dig2bXFVV /x8RzR/wFd8yUHTaT+AWkA== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +AZURe.com. IN DS +SECTION AUTHORITY +2VPE4QITM4PLV486G56AB7OUDVIO1U3D.com. 21574 IN NSEC3 1 1 0 - 2vpf134m9p90k8k9jmtdjemi4anqnsaf NS DS RRSIG +2VPE4QITM4PLV486G56AB7OUDVIO1U3D.com. 21574 IN RRSIG NSEC3 8 2 86400 20200726043358 20200719032358 24966 com. SpLKFkZ534rLzf6WTHf+JdBCanfiUhX1 BbmjpFQRI+l1qRN9po9mtA95jfw8AGjw S0n36LK9MBNF4pRo4zcmxny3/K7lfDwf 6HhDlNMp3KQGtLMGcSCqw7StxA3b3qsg 0NhxPtHl818RsBKueIR88LPX88x5jkzC Gr11WJoVlq682pCJlmkVOze2JCmRvjYY m/mUHv4y5+mh3h2AbHc+zA== +CK0POJMG874LJREF7EFN8430QVIT8BSM.com. 21574 IN NSEC3 1 1 0 - ck0q1gin43n1arrc9osm6qpqr81h5m9a NS SOA RRSIG DNSKEY NSEC3PARAM +CK0POJMG874LJREF7EFN8430QVIT8BSM.com. 21574 IN RRSIG NSEC3 8 2 86400 20200728044213 20200721033213 24966 com. qoAss2VVxsBWG9+MAQ3giAmCnzf4dz7G ppZsQfkt3b7cFIVgO3TiWPWkusb3BEbp sma2Q+x/TBLeeHYm6l1HWXMIl7zcYLsA XY0ZzoWaqNPTPH6YNDAreZYP21UekBL7 g710cndRk4oaJUNz5t8sGi3JaOJF046Q cUz6gGg7NLMvyGlJWzmftGbxgp9ovdOg wmirddESGOj33kuCfSJvWA== +com. 874 IN RRSIG SOA 8 1 900 20200729100807 20200722085807 24966 com. kDvHo8x7ut5Lu66MSUOUTPvxfYJLXMcu aKPCu9I+jTYZOzAH4KquPm+765a/gnp5 2okPhEUJTO2JYumhfkjyG04kE4HCnqfR bWtjjfIyqXo34km+CR8rG8RGZ6QilLWZ 0yxux5+izvuji4L4KLeTxPwUJQFgAmVA 59unj2IqysGWRc2ETSofHOPFrydduuyc DJdQLN6Dq1fMFh849Qr6nw== +com. 874 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1595412487 1800 900 604800 86400 +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +AZURe.COM. IN NS +SECTION ANSWER +AZURe.COM. 21462 IN NS ns1-205.azure-dns.COM. +AZURe.COM. 21462 IN NS ns2-205.azure-dns.net. +AZURe.COM. 21462 IN NS ns3-205.azure-dns.org. +AZURe.COM. 21462 IN NS ns4-205.azure-dns.info. +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +DigitRaNsit-aKS.WESTEuROPe.CloUDApp.aZuRe.cOm. IN A +SECTION ANSWER +DigitRaNsit-aKS.WESTEuROPe.CloUDApp.aZuRe.cOm. 9 IN A 40.119.148.209 +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA NOERROR +SECTION QUESTION +api.digitransit.fi. IN A +SECTION ANSWER +api.digitransit.fi. 3357 IN CNAME digitransit-prod.trafficmanager.net. +digitransit-aks.westeurope.cloudapp.azure.com. 9 IN A 40.119.148.209 +digitransit-prod.trafficmanager.net. 299 IN CNAME digitransit-aks.westeurope.cloudapp.azure.com. +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +DigitRaNsit-aKS.WESTEuROPe.CloUDApp.aZuRe.cOm. IN A +SECTION ANSWER +DigitRaNsit-aKS.WESTEuROPe.CloUDApp.aZuRe.cOm. 9 IN A 40.119.148.209 +ENTRY_END + +RANGE_END + + +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +api.digitransit.fi. IN A +ENTRY_END + +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +api.digitransit.fi. IN A +SECTION ANSWER +api.digitransit.fi. IN CNAME digitransit-prod.trafficmanager.net. +digitransit-prod.trafficmanager.net. IN CNAME digitransit-aks.westeurope.cloudapp.azure.com. +digitransit-aks.westeurope.cloudapp.azure.com. IN A 40.119.148.209 +ENTRY_END + +SCENARIO_END diff -Nru knot-resolver-5.1.1/lib/layer/validate.test.integr/kresd_config.j2 knot-resolver-5.2.1/lib/layer/validate.test.integr/kresd_config.j2 --- knot-resolver-5.1.1/lib/layer/validate.test.integr/kresd_config.j2 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/lib/layer/validate.test.integr/kresd_config.j2 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,52 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +trust_anchors.remove('.') +{% for TAF in TRUST_ANCHOR_FILES %} +trust_anchors.add_file('{{TAF}}') +{% endfor %} + +{% raw %} +-- Disable RFC5011 TA update +if ta_update then + modules.unload('ta_update') +end + +-- Disable RFC8145 signaling, scenario doesn't provide expected answers +if ta_signal_query then + modules.unload('ta_signal_query') +end + +-- Disable RFC8109 priming, scenario doesn't provide expected answers +if priming then + modules.unload('priming') +end + +-- Disable this module because it make one priming query +if detect_time_skew then + modules.unload('detect_time_skew') +end + +cache.size = 2*MB +verbose(true) +policy.add(policy.all(policy.DEBUG_ALWAYS)) +policy.add(policy.all(policy.FORWARD('8.8.8.8'))) +{% endraw %} + +net = { '{{SELF_ADDR}}' } + +-- Self-checks on globals +assert(help() ~= nil) +assert(worker.id ~= nil) +-- Self-checks on facilities +assert(cache.count() == 0) +assert(cache.stats() ~= nil) +assert(cache.backends() ~= nil) +assert(worker.stats() ~= nil) +assert(net.interfaces() ~= nil) +-- Self-checks on loaded stuff +assert(net.list()[1].transport.ip == '{{SELF_ADDR}}') +assert(#modules.list() > 0) +-- Self-check timers +ev = event.recurrent(1 * sec, function (ev) return 1 end) +event.cancel(ev) +ev = event.after(0, function (ev) return 1 end) diff -Nru knot-resolver-5.1.1/lib/layer.h knot-resolver-5.2.1/lib/layer.h --- knot-resolver-5.1.1/lib/layer.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/layer.h 2020-12-09 09:44:29.000000000 +0000 @@ -48,7 +48,14 @@ /** Check that a kr_layer_state makes sense. We're not very strict ATM. */ static inline bool kr_state_consistent(enum kr_layer_state s) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare" +#endif return s >= 0 && s < (1 << 5); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif } /* Forward declarations. */ @@ -92,7 +99,8 @@ int (*checkout)(kr_layer_t *ctx, knot_pkt_t *packet, struct sockaddr *dst, int type); /** Finalises the answer. - * Last chance to affect what will get into the answer, including EDNS.*/ + * Last chance to affect what will get into the answer, including EDNS. + * Not called if the packet is being dropped. */ int (*answer_finalize)(kr_layer_t *ctx); /** The C module can store anything in here. */ diff -Nru knot-resolver-5.1.1/lib/meson.build knot-resolver-5.2.1/lib/meson.build --- knot-resolver-5.1.1/lib/meson.build 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/meson.build 2020-12-09 09:44:29.000000000 +0000 @@ -74,8 +74,10 @@ ] integr_tests += [ - ['cache_minimal_nsec', join_paths(meson.current_source_dir(), 'cache', 'test.integr')], - ['iter_limits' , join_paths(meson.current_source_dir(), 'layer', 'test.integr')], + ['cache_overflow', meson.current_source_dir() / 'cache' / 'overflow.test.integr'], + ['cache_minimal_nsec', meson.current_source_dir() / 'cache' / 'test.integr'], + ['iter_limits' , meson.current_source_dir() / 'layer' / 'test.integr'], + ['validate' , meson.current_source_dir() / 'layer' / 'validate.test.integr'], ] libkres_inc = include_directories('..') diff -Nru knot-resolver-5.1.1/lib/README.rst knot-resolver-5.2.1/lib/README.rst --- knot-resolver-5.1.1/lib/README.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/README.rst 2020-12-09 09:44:29.000000000 +0000 @@ -270,8 +270,8 @@ req:pop(qry) -.. _libknot: https://gitlab.labs.nic.cz/knot/knot-dns/tree/master/src/libknot -.. _bindings: https://gitlab.labs.nic.cz/knot/knot-resolver/blob/master/daemon/lua/kres.lua +.. _libknot: https://gitlab.nic.cz/knot/knot-dns/tree/master/src/libknot +.. _bindings: https://gitlab.nic.cz/knot/knot-resolver/blob/master/daemon/lua/kres.lua .. _significant-lua-changes: diff -Nru knot-resolver-5.1.1/lib/resolve.c knot-resolver-5.2.1/lib/resolve.c --- knot-resolver-5.1.1/lib/resolve.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/resolve.c 2020-12-09 09:44:29.000000000 +0000 @@ -18,13 +18,13 @@ #include "lib/layer/iterate.h" #include "lib/dnssec/ta.h" #include "lib/dnssec.h" -#if defined(ENABLE_COOKIES) +#if ENABLE_COOKIES #include "lib/cookies/control.h" #include "lib/cookies/helper.h" #include "lib/cookies/nonce.h" #else /* Define compatibility macros */ #define KNOT_EDNS_OPTION_COOKIE 10 -#endif /* defined(ENABLE_COOKIES) */ +#endif /* ENABLE_COOKIES */ #define VERBOSE_MSG(qry, ...) QRVERBOSE((qry), "resl", __VA_ARGS__) @@ -46,6 +46,17 @@ } } +bool kr_rank_test(uint8_t rank, uint8_t kr_flag) +{ + assert(kr_rank_check(rank) && kr_rank_check(kr_flag)); + if (kr_flag == KR_RANK_AUTH) { + return rank & KR_RANK_AUTH; + } + assert(!(kr_flag & KR_RANK_AUTH)); + /* The rest are exclusive values - exactly one has to be set. */ + return (rank & ~KR_RANK_AUTH) == kr_flag; +} + /** @internal Set @a yielded to all RRs with matching @a qry_uid. */ static void set_yield(ranked_rr_array_t *array, const uint32_t qry_uid, const bool yielded) { @@ -409,16 +420,16 @@ return knot_pkt_reserve(pkt, len); } -static int edns_create(knot_pkt_t *pkt, knot_pkt_t *template, struct kr_request *req) +static int edns_create(knot_pkt_t *pkt, const struct kr_request *req) { - pkt->opt_rr = knot_rrset_copy(req->ctx->opt_rr, &pkt->mm); + pkt->opt_rr = knot_rrset_copy(req->ctx->upstream_opt_rr, &pkt->mm); size_t wire_size = knot_edns_wire_size(pkt->opt_rr); -#if defined(ENABLE_COOKIES) +#if ENABLE_COOKIES if (req->ctx->cookie_ctx.clnt.enabled || req->ctx->cookie_ctx.srvr.enabled) { wire_size += KR_COOKIE_OPT_MAX_LEN; } -#endif /* defined(ENABLE_COOKIES) */ +#endif /* ENABLE_COOKIES */ if (req->qsource.flags.tls) { if (req->ctx->tls_padding == -1) /* FIXME: we do not know how to reserve space for the @@ -431,26 +442,6 @@ return knot_pkt_reserve(pkt, wire_size); } -static int answer_prepare(struct kr_request *req, knot_pkt_t *query) -{ - knot_pkt_t *answer = req->answer; - if (knot_pkt_init_response(answer, query) != 0) { - return kr_error(ENOMEM); /* Failed to initialize answer */ - } - /* Handle EDNS in the query */ - if (knot_pkt_has_edns(query)) { - answer->opt_rr = knot_rrset_copy(req->ctx->opt_rr, &answer->mm); - if (answer->opt_rr == NULL){ - return kr_error(ENOMEM); - } - /* Set DO bit if set (DNSSEC requested). */ - if (knot_pkt_has_dnssec(query)) { - knot_edns_set_do(answer->opt_rr); - } - } - return kr_ok(); -} - /** * @param all_secure optionally &&-combine security of written RRs into its value. * (i.e. if you pass a pointer to false, it will always remain) @@ -542,6 +533,9 @@ static void answer_fail(struct kr_request *request) { /* Note: OPT in SERVFAIL response is still useful for cookies/additional info. */ + if (VERBOSE_STATUS || kr_log_rtrace_enabled(request)) + kr_log_req(request, 0, 0, "resl", + "request failed, answering with empty SERVFAIL\n"); knot_pkt_t *answer = request->answer; knot_rrset_t *opt_rr = answer->opt_rr; /* it gets NULLed below */ int ret = kr_pkt_clear_payload(answer); @@ -573,6 +567,7 @@ { struct kr_rplan *rplan = &request->rplan; knot_pkt_t *answer = request->answer; + const uint8_t *q_wire = request->qsource.packet->wire; if (answer->rrset_count != 0) { /* Non-standard: we assume the answer had been constructed. @@ -611,7 +606,7 @@ /* TODO: clean this up in !660 or followup, and it isn't foolproof anyway. */ if (last->flags.DNSSEC_BOGUS || (rplan->pending.len > 0 && array_tail(rplan->pending)->flags.DNSSEC_BOGUS)) { - if (!knot_wire_get_cd(request->qsource.packet->wire)) { + if (!knot_wire_get_cd(q_wire)) { answer_fail(request); return; } @@ -626,7 +621,7 @@ secure = false; /* don't trust forwarding for now */ } if (last && (last->flags.DNSSEC_OPTOUT)) { - VERBOSE_MSG(NULL, "AD: opt-out\n"); + VERBOSE_MSG(last, "insecure because of opt-out\n"); secure = false; /* the last answer is insecure due to opt-out */ } @@ -676,9 +671,10 @@ VERBOSE_MSG(last, "AD: request%s classified as SECURE\n", secure ? "" : " NOT"); request->rank = secure ? KR_RANK_SECURE : KR_RANK_INITIAL; - /* Clear AD if not secure. ATM answer has AD=1 if requested secured answer. */ - if (!secure) { - knot_wire_clear_ad(answer->wire); + /* Set AD if secure and AD bit "was requested". */ + if (secure && !knot_wire_get_cd(q_wire) + && (knot_pkt_has_dnssec(answer) || knot_wire_get_ad(q_wire))) { + knot_wire_set_ad(answer->wire); } } @@ -690,7 +686,7 @@ /* Remove any EDNS records from any previous iteration. */ int ret = edns_erase_and_reserve(pkt); if (ret) return ret; - ret = edns_create(pkt, request->answer, request); + ret = edns_create(pkt, request); if (ret) return ret; if (qry->flags.STUB) { /* Stub resolution (ask for +rd and +do) */ @@ -712,11 +708,11 @@ return kr_ok(); } -int kr_resolve_begin(struct kr_request *request, struct kr_context *ctx, knot_pkt_t *answer) +int kr_resolve_begin(struct kr_request *request, struct kr_context *ctx) { /* Initialize request */ request->ctx = ctx; - request->answer = answer; + request->answer = NULL; request->options = ctx->options; request->state = KR_STATE_CONSUME; request->current_query = NULL; @@ -762,25 +758,11 @@ qry->flags.AWAIT_CUT = true; /* Want DNSSEC if it's posible to secure this name (e.g. is covered by any TA) */ if ((knot_wire_get_ad(packet->wire) || knot_pkt_has_dnssec(packet)) && - kr_ta_covers_qry(request->ctx, qname, qtype)) { + kr_ta_covers_qry(request->ctx, qry->sname, qtype)) { qry->flags.DNSSEC_WANT = true; } } - /* Initialize answer packet */ - knot_pkt_t *answer = request->answer; - knot_wire_set_qr(answer->wire); - knot_wire_clear_aa(answer->wire); - knot_wire_set_ra(answer->wire); - knot_wire_set_rcode(answer->wire, KNOT_RCODE_NOERROR); - - assert(request->qsource.packet); - if (knot_wire_get_cd(request->qsource.packet->wire)) { - knot_wire_set_cd(answer->wire); - } else if (qry->flags.DNSSEC_WANT) { - knot_wire_set_ad(answer->wire); - } - /* Expect answer, pop if satisfied immediately */ ITERATE_LAYERS(request, qry, begin); if ((request->state & KR_STATE_DONE) != 0) { @@ -794,6 +776,69 @@ return request->state; } +knot_pkt_t * kr_request_ensure_answer(struct kr_request *request) +{ + if (request->answer) + return request->answer; + + const knot_pkt_t *qs_pkt = request->qsource.packet; + assert(qs_pkt); + // Find answer_max: limit on DNS wire length. + uint16_t answer_max; + const struct kr_request_qsource_flags *qs_flags = &request->qsource.flags; + assert((qs_flags->tls || qs_flags->http) ? qs_flags->tcp : true); + if (!request->qsource.addr || qs_flags->tcp) { + // not on UDP + answer_max = KNOT_WIRE_MAX_PKTSIZE; + } else if (knot_pkt_has_edns(qs_pkt)) { + // UDP with EDNS + answer_max = MIN(knot_edns_get_payload(qs_pkt->opt_rr), + knot_edns_get_payload(request->ctx->downstream_opt_rr)); + answer_max = MAX(answer_max, KNOT_WIRE_MIN_PKTSIZE); + } else { + // UDP without EDNS + answer_max = KNOT_WIRE_MIN_PKTSIZE; + } + + // Allocate the packet. + uint8_t *wire = NULL; + if (request->alloc_wire_cb) { + wire = request->alloc_wire_cb(request, &answer_max); + if (!wire) + goto enomem; + } + knot_pkt_t *answer = request->answer = + knot_pkt_new(wire, answer_max, &request->pool); + if (!answer || knot_pkt_init_response(answer, qs_pkt) != 0) { + assert(!answer); // otherwise we messed something up + goto enomem; + } + if (!wire) + wire = answer->wire; + + // Much was done by knot_pkt_init_response() + knot_wire_set_ra(wire); + knot_wire_set_rcode(wire, KNOT_RCODE_NOERROR); + if (knot_wire_get_cd(qs_pkt->wire)) { + knot_wire_set_cd(wire); + } + + // Prepare EDNS if required. + if (knot_pkt_has_edns(qs_pkt)) { + answer->opt_rr = knot_rrset_copy(request->ctx->downstream_opt_rr, + &answer->mm); + if (!answer->opt_rr) + goto enomem; // answer is on mempool, so "leak" is OK + if (knot_pkt_has_dnssec(qs_pkt)) + knot_edns_set_do(answer->opt_rr); + } + + return request->answer; +enomem: + request->state = KR_STATE_FAIL; // TODO: really combine with another flag? + return request->answer = NULL; +} + KR_PURE static bool kr_inaddr_equal(const struct sockaddr *a, const struct sockaddr *b) { const int a_len = kr_inaddr_len(a); @@ -890,9 +935,6 @@ /* Empty resolution plan, push packet as the new query */ if (packet && kr_rplan_empty(rplan)) { - if (answer_prepare(request, packet) != 0) { - return KR_STATE_FAIL; - } return resolve_query(request, packet); } @@ -947,7 +989,7 @@ kr_log_req(request, 0, 2, "resl", "=> too many failures in a row, " "bail out (mitigation for NXNSAttack " - "CVE-2020-12667)"); + "CVE-2020-12667)\n"); } return KR_STATE_FAIL; } @@ -1200,6 +1242,7 @@ if (qry->flags.DNSSEC_NODS) { /* This is the next query iteration with minimized qname. * At previous iteration DS non-existance has been proven */ + VERBOSE_MSG(qry, "<= DS doesn't exist, going insecure\n"); qry->flags.DNSSEC_NODS = false; qry->flags.DNSSEC_WANT = false; qry->flags.DNSSEC_INSECURE = true; @@ -1493,7 +1536,7 @@ return request->state; } -#if defined(ENABLE_COOKIES) +#if ENABLE_COOKIES /** Update DNS cookie data in packet. */ static bool outbound_request_update_cookies(struct kr_request *req, const struct sockaddr *src, @@ -1523,7 +1566,7 @@ return true; } -#endif /* defined(ENABLE_COOKIES) */ +#endif /* ENABLE_COOKIES */ int kr_resolve_checkout(struct kr_request *request, const struct sockaddr *src, struct sockaddr *dst, int type, knot_pkt_t *packet) @@ -1542,7 +1585,7 @@ } struct kr_query *qry = array_tail(rplan->pending); -#if defined(ENABLE_COOKIES) +#if ENABLE_COOKIES /* Update DNS cookies in request. */ if (type == SOCK_DGRAM) { /* @todo: Add cookies also over TCP? */ /* @@ -1554,7 +1597,7 @@ return kr_error(EINVAL); } } -#endif /* defined(ENABLE_COOKIES) */ +#endif /* ENABLE_COOKIES */ int ret = query_finalize(request, qry, packet); if (ret != 0) { @@ -1618,20 +1661,23 @@ int kr_resolve_finish(struct kr_request *request, int state) { request->state = state; - /* Finalize answer and construct wire-buffer. */ - ITERATE_LAYERS(request, NULL, answer_finalize); - answer_finalize(request); - - /* Defensive style, in case someone has forgotten. - * Beware: non-empty answers do make sense even with SERVFAIL case, etc. */ - if (request->state != KR_STATE_DONE) { - uint8_t *wire = request->answer->wire; - switch (knot_wire_get_rcode(wire)) { - case KNOT_RCODE_NOERROR: - case KNOT_RCODE_NXDOMAIN: - knot_wire_clear_ad(wire); - knot_wire_clear_aa(wire); - knot_wire_set_rcode(wire, KNOT_RCODE_SERVFAIL); + /* Finalize answer and construct whole wire-format (unless dropping). */ + knot_pkt_t *answer = kr_request_ensure_answer(request); + if (answer) { + ITERATE_LAYERS(request, NULL, answer_finalize); + answer_finalize(request); + + /* Defensive style, in case someone has forgotten. + * Beware: non-empty answers do make sense even with SERVFAIL case, etc. */ + if (request->state != KR_STATE_DONE) { + uint8_t *wire = answer->wire; + switch (knot_wire_get_rcode(wire)) { + case KNOT_RCODE_NOERROR: + case KNOT_RCODE_NXDOMAIN: + knot_wire_clear_ad(wire); + knot_wire_clear_aa(wire); + knot_wire_set_rcode(wire, KNOT_RCODE_SERVFAIL); + } } } @@ -1640,7 +1686,7 @@ #ifndef NOVERBOSELOG struct kr_rplan *rplan = &request->rplan; struct kr_query *last = kr_rplan_last(rplan); - VERBOSE_MSG(last, "finished: %d, queries: %zu, mempool: %zu B\n", + VERBOSE_MSG(last, "finished in state: %d, queries: %zu, mempool: %zu B\n", request->state, rplan->resolved.len, (size_t) mp_total_size(request->pool.ctx)); #endif diff -Nru knot-resolver-5.1.1/lib/resolve.h knot-resolver-5.2.1/lib/resolve.h --- knot-resolver-5.1.1/lib/resolve.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/resolve.h 2020-12-09 09:44:29.000000000 +0000 @@ -36,7 +36,7 @@ * }; * * // Setup and provide input query - * int state = kr_resolve_begin(&req, ctx, final_answer); + * int state = kr_resolve_begin(&req, ctx); * state = kr_resolve_consume(&req, query); * * // Generate answer @@ -61,6 +61,20 @@ */ +struct kr_request; +/** Allocate buffer for answer's wire (*maxlen may get lowered). + * + * Motivation: XDP wire allocation is an overlap of library and daemon: + * - it needs to be called from the library + * - it needs to rely on some daemon's internals + * - the library (currently) isn't allowed to directly use symbols from daemon + * (contrary to modules), e.g. some of our lib-using tests run without daemon + * + * Note: after we obtain the wire, we're obliged to send it out. + * (So far there's no use case to allow cancelling at that point.) + */ +typedef uint8_t * (*alloc_wire_f)(struct kr_request *req, uint16_t *maxlen); + /** * RRset rank - for cache and ranked_rr_*. * @@ -90,7 +104,7 @@ KR_RANK_INDET = 4, /**< Unable to determine whether it should be secure. */ KR_RANK_BOGUS, /**< Ought to be secure but isn't. */ KR_RANK_MISMATCH, - KR_RANK_MISSING, /**< Unable to obtain a good signature. */ + KR_RANK_MISSING, /**< No RRSIG found for that owner+type combination. */ /** Proven to be insecure, i.e. we have a chain of trust from TAs * that cryptographically denies the possibility of existence @@ -110,16 +124,7 @@ bool kr_rank_check(uint8_t rank) KR_PURE; /** Test the presence of any flag/state in a rank, i.e. including KR_RANK_AUTH. */ -static inline bool kr_rank_test(uint8_t rank, uint8_t kr_flag) -{ - assert(kr_rank_check(rank) && kr_rank_check(kr_flag)); - if (kr_flag == KR_RANK_AUTH) { - return rank & KR_RANK_AUTH; - } - assert(!(kr_flag & KR_RANK_AUTH)); - /* The rest are exclusive values - exactly one has to be set. */ - return (rank & ~KR_RANK_AUTH) == kr_flag; -} +bool kr_rank_test(uint8_t rank, uint8_t kr_flag) KR_PURE KR_EXPORT; /** Set the rank state. The _AUTH flag is kept as it was. */ static inline void kr_rank_set(uint8_t *rank, uint8_t kr_flag) @@ -149,7 +154,8 @@ /** Default EDNS towards *both* clients and upstream. * LATER: consider splitting the two, e.g. allow separately * configured limits for UDP packet size (say, LAN is under control). */ - knot_rrset_t *opt_rr; + knot_rrset_t *downstream_opt_rr; + knot_rrset_t *upstream_opt_rr; map_t trust_anchors; map_t negative_anchors; @@ -173,6 +179,7 @@ bool tcp:1; /**< true if the request is not on UDP; only meaningful if (dst_addr). */ bool tls:1; /**< true if the request is encrypted; only meaningful if (dst_addr). */ bool http:1; /**< true if the request is on HTTP; only meaningful if (dst_addr). */ + bool xdp:1; /**< true if the request is on AF_XDP; only meaningful if (dst_addr). */ }; /** @@ -187,7 +194,7 @@ */ struct kr_request { struct kr_context *ctx; - knot_pkt_t *answer; + knot_pkt_t *answer; /**< See kr_request_ensure_answer() */ struct kr_query *current_query; /**< Current evaluated query. */ struct { /** Address that originated the request. NULL for internal origin. */ @@ -199,6 +206,7 @@ const knot_pkt_t *packet; struct kr_request_qsource_flags flags; /**< See definition above. */ size_t size; /**< query packet size */ + int32_t stream_id; /**< HTTP/2 stream ID for DoH requests */ } qsource; struct { unsigned rtt; /**< Current upstream RTT */ @@ -226,9 +234,10 @@ trace_callback_f trace_finish; /**< Request finish tracepoint */ int vars_ref; /**< Reference to per-request variable table. LUA_NOREF if not set. */ knot_mm_t pool; - unsigned int uid; /** for logging purposes only */ + unsigned int uid; /**< for logging purposes only */ unsigned int count_no_nsaddr; unsigned int count_fail_row; + alloc_wire_f alloc_wire_cb; /**< CB to allocate answer wire (can be NULL). */ }; /** Initializer for an array of *_selected. */ @@ -241,22 +250,31 @@ /** * Begin name resolution. * - * @note Expects a request to have an initialized mempool, the "answer" packet will - * be kept during the resolution and will contain the final answer at the end. + * @note Expects a request to have an initialized mempool. * * @param request request state with initialized mempool * @param ctx resolution context - * @param answer allocated packet for final answer * @return CONSUME (expecting query) */ KR_EXPORT -int kr_resolve_begin(struct kr_request *request, struct kr_context *ctx, knot_pkt_t *answer); +int kr_resolve_begin(struct kr_request *request, struct kr_context *ctx); + +/** + * Ensure that request->answer is usable, and return it (for convenience). + * + * It may return NULL, in which case it marks ->state with _FAIL and no answer will be sent. + * Only use this when it's guaranteed that there will be no delay before sending it. + * You don't need to call this in places where "resolver knows" that there will be no delay, + * but even there you need to check if the ->answer is NULL (unless you check for _FAIL anyway). + */ +KR_EXPORT +knot_pkt_t * kr_request_ensure_answer(struct kr_request *request); /** * Consume input packet (may be either first query or answer to query originated from kr_resolve_produce()) * * @note If the I/O fails, provide an empty or NULL packet, this will make iterator recognize nameserver failure. - * + * * @param request request state (awaiting input) * @param src [in] packet source address * @param packet [in] input packet @@ -271,7 +289,7 @@ * If the CONSUME is returned then dst, type and packet will be filled with * appropriate values and caller is responsible to send them and receive answer. * If it returns any other state, then content of the variables is undefined. - * + * * @param request request state (in PRODUCE state) * @param dst [out] possible address of the next nameserver * @param type [out] possible used socket type (SOCK_STREAM, SOCK_DGRAM) diff -Nru knot-resolver-5.1.1/lib/rplan.h knot-resolver-5.2.1/lib/rplan.h --- knot-resolver-5.1.1/lib/rplan.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/rplan.h 2020-12-09 09:44:29.000000000 +0000 @@ -30,7 +30,7 @@ bool EXPIRING : 1; /**< Query response is cached, but expiring. */ bool ALLOW_LOCAL : 1; /**< Allow queries to local or private address ranges. */ bool DNSSEC_WANT : 1; /**< Want DNSSEC secured answer; exception: +cd, - * i.e. knot_wire_set_cd(request->answer->wire). */ + * i.e. knot_wire_get_cd(request->qsource.packet->wire) */ bool DNSSEC_BOGUS : 1; /**< Query response is DNSSEC bogus. */ bool DNSSEC_INSECURE : 1;/**< Query response is DNSSEC insecure. */ bool DNSSEC_CD : 1; /**< Instruction to set CD bit in request. */ diff -Nru knot-resolver-5.1.1/lib/utils.c knot-resolver-5.2.1/lib/utils.c --- knot-resolver-5.1.1/lib/utils.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/utils.c 2020-12-09 09:44:29.000000000 +0000 @@ -210,6 +210,31 @@ return result; } +char * kr_absolutize_path(const char *dirname, const char *fname) +{ + assert(dirname && fname); + char *result; + int aret; + if (dirname[0] == '/') { // absolute path is easier + aret = asprintf(&result, "%s/%s", dirname, fname); + } else { // relative path, but don't resolve symlinks + char buf[PATH_MAX]; + const char *cwd = getcwd(buf, sizeof(buf)); + if (!cwd) + return NULL; // errno has been set already + if (strcmp(dirname, ".") == 0) { + // get rid of one common case of extraneous "./" + aret = asprintf(&result, "%s/%s", cwd, fname); + } else { + aret = asprintf(&result, "%s/%s/%s", cwd, dirname, fname); + } + } + if (aret > 0) + return result; + errno = -aret; + return NULL; +} + int kr_memreserve(void *baton, void **mem, size_t elm_size, size_t want, size_t *have) { if (*have >= want) { @@ -1056,8 +1081,7 @@ for (knot_section_t i = KNOT_ANSWER; i <= KNOT_ADDITIONAL; ++i) { const knot_pktsection_t *sec = knot_pkt_section(pkt, i); - if (sec->count == 0 || knot_pkt_rr(sec, 0)->type == KNOT_RRTYPE_OPT) { - /* OPT RRs are _supposed_ to be the last ^^, if they appear */ + if (sec->count == 0) { continue; } @@ -1266,3 +1290,8 @@ return buf.f_frsize * buf.f_blocks; } +const char * kr_dirent_name(const struct dirent *de) +{ + return de ? de->d_name : NULL; +} + diff -Nru knot-resolver-5.1.1/lib/utils.h knot-resolver-5.2.1/lib/utils.h --- knot-resolver-5.1.1/lib/utils.h 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/lib/utils.h 2020-12-09 09:44:29.000000000 +0000 @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include @@ -76,7 +77,7 @@ /** * Log a message through the request log handler or stdout. * Caller is responsible for detecting verbose mode, use QRVERBOSE() macro. - * @param query current query + * @param qry current query * @param source message source * @param fmt message format */ @@ -223,6 +224,11 @@ KR_EXPORT char* kr_strcatdup(unsigned n, ...); +/** Construct absolute file path, without resolving symlinks. + * \return malloc-ed string or NULL (+errno in that case) */ +KR_EXPORT +char * kr_absolutize_path(const char *dirname, const char *fname); + /** You probably want kr_rand_* convenience functions instead. * This is a buffered version of gnutls_rnd(GNUTLS_RND_NONCE, ..) */ KR_EXPORT @@ -367,10 +373,10 @@ int kr_straddr_subnet(void *dst, const char *addr); /** Splits ip address specified as "addr@port" or "addr#port" into addr and port. - * \param instr[in] zero-terminated input, e.g. "192.0.2.1#12345\0" - * \param ipaddr[out] working buffer for the port-less prefix of instr; + * \param[in] instr zero-terminated input, e.g. "192.0.2.1#12345\0" + * \param[out] ipaddr working buffer for the port-less prefix of instr; * length >= INET6_ADDRSTRLEN + 1. - * \param port[out] written in case it's specified in instr + * \param[out] port written in case it's specified in instr * \return error code * \note Typically you follow this by kr_straddr_socket(). * \note Only internet addresses are supported, i.e. no AF_UNIX sockets. @@ -536,8 +542,8 @@ /** * Difference between two calendar times specified as strings. - * \param format[in] format for strptime - * \param diff[out] result from C difftime(time1, time0) + * \param[in] format format for strptime + * \param[out] diff result from C difftime(time1, time0) */ KR_EXPORT const char *kr_strptime_diff(const char *format, const char *time1_str, @@ -556,4 +562,6 @@ KR_EXPORT time_t kr_file_mtime (const char* fname); /** Return filesystem size in bytes. */ KR_EXPORT long long kr_fssize(const char *path); +/** Simply return de->dname. (useful from Lua) */ +KR_EXPORT const char * kr_dirent_name(const struct dirent *de); diff -Nru knot-resolver-5.1.1/.luacheckrc knot-resolver-5.2.1/.luacheckrc --- knot-resolver-5.1.1/.luacheckrc 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/.luacheckrc 2020-12-09 09:44:29.000000000 +0000 @@ -2,6 +2,7 @@ std = 'luajit' new_read_globals = { 'cache', + 'eval_cmd', 'event', 'help', '_hint_root_file', @@ -19,6 +20,7 @@ 'user', 'verbose', 'worker', + 'kluautil_list_dir', -- Sandbox declarations 'kB', 'MB', @@ -40,7 +42,6 @@ 'libknot_SONAME', 'libzscanner_SONAME', 'table_print', - '__engine', '_ENV', } diff -Nru knot-resolver-5.1.1/.mailmap knot-resolver-5.2.1/.mailmap --- knot-resolver-5.1.1/.mailmap 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/.mailmap 2020-12-09 09:44:29.000000000 +0000 @@ -12,6 +12,8 @@ Hasnat Jiří Helebrant Ivana Krumlová +Jakub Ružička +Jan Hák Jan Holuša Jan Pavlinec Jan Včelák @@ -35,6 +37,7 @@ rickhg12hs Robert Šefr SH +Simon South Štěpán Balážik Štěpán Kotek Štěpán Kotek @@ -43,6 +46,7 @@ Tomáš Křížek Ulrich Wisser Leo Vandewoestijne +Vašek Šraier Vicky Shrestha Vítězslav Kříž Vladimír Čunát diff -Nru knot-resolver-5.1.1/meson.build knot-resolver-5.2.1/meson.build --- knot-resolver-5.1.1/meson.build 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/meson.build 2020-12-09 09:44:29.000000000 +0000 @@ -4,16 +4,16 @@ 'knot-resolver', ['c', 'cpp'], license: 'GPLv3+', - version: '5.1.1', + version: '5.2.1', default_options: ['c_std=gnu11', 'b_ndebug=if-release'], - meson_version: '>=0.46', + meson_version: '>=0.49', ) # Unity build if get_option('unity') != 'off' error('unity builds are not supported! ' + - 'https://gitlab.labs.nic.cz/knot/knot-resolver/issues/460') + 'https://gitlab.nic.cz/knot/knot-resolver/issues/460') endif @@ -45,24 +45,26 @@ ## Paths prefix = get_option('prefix') -data_dir = join_paths(prefix, get_option('datadir'), 'knot-resolver') -doc_dir = join_paths(prefix, get_option('datadir'), 'doc', 'knot-resolver') -examples_dir = join_paths(doc_dir, 'examples') -etc_dir = join_paths(prefix, get_option('sysconfdir'), 'knot-resolver') -lib_dir = join_paths(prefix, get_option('libdir'), 'knot-resolver') -modules_dir = join_paths(lib_dir, 'kres_modules') -sbin_dir = join_paths(prefix, get_option('sbindir')) -run_dir = join_paths('/run', 'knot-resolver') -systemd_work_dir = join_paths(prefix, get_option('localstatedir'), 'lib', 'knot-resolver') -systemd_cache_dir = join_paths(prefix, get_option('localstatedir'), 'cache', 'knot-resolver') -systemd_unit_dir = join_paths(prefix, 'lib', 'systemd', 'system') -systemd_tmpfiles_dir = join_paths(prefix, 'lib', 'tmpfiles.d') +data_dir = prefix / get_option('datadir') / 'knot-resolver' +doc_dir = prefix / get_option('datadir') / 'doc' / 'knot-resolver' +info_dir = prefix / get_option('datadir') / 'info' +examples_dir = doc_dir / 'examples' +etc_dir = prefix / get_option('sysconfdir') / 'knot-resolver' +lib_dir = prefix / get_option('libdir') / 'knot-resolver' +modules_dir = lib_dir / 'kres_modules' +sbin_dir = prefix / get_option('sbindir') +run_dir = '/run' / 'knot-resolver' +systemd_work_dir = prefix / get_option('localstatedir') / 'lib' / 'knot-resolver' +systemd_cache_dir = prefix / get_option('localstatedir') / 'cache' / 'knot-resolver' +systemd_unit_dir = prefix / 'lib' / 'systemd' / 'system' +systemd_tmpfiles_dir = prefix / 'lib' / 'tmpfiles.d' +systemd_sysusers_dir = prefix / 'lib' / 'sysusers.d' mod_inc_dir = include_directories('.', 'contrib/') ## Trust anchors managed_ta = get_option('managed_ta') == 'enabled' -keyfile_default = join_paths(etc_dir, get_option('keyfile_default')) -if keyfile_default == join_paths(etc_dir, 'root.keys') +keyfile_default = etc_dir / get_option('keyfile_default') +if keyfile_default == etc_dir / 'root.keys' managed_ta = managed_ta or get_option('managed_ta') == 'auto' endif install_root_keys = get_option('install_root_keys') == 'enabled' @@ -72,8 +74,8 @@ ## Root hints -root_hints = join_paths(etc_dir, get_option('root_hints')) -if root_hints == join_paths(etc_dir, 'root.hints') +root_hints = etc_dir / get_option('root_hints') +if root_hints == etc_dir / 'root.hints' install_root_hints = true else install_root_hints = false @@ -86,12 +88,17 @@ ## Optional dependencies message('--- optional dependencies ---') -capng = dependency('libcap-ng', required: false) +nghttp2 = dependency('libnghttp2', required: false) openssl = dependency('openssl', required: false) have_asprintf = meson.get_compiler('c').has_function('asprintf', prefix: '#define _GNU_SOURCE\n#include ') +### capng +# use empty name to disable the dependency, but still compile the dependent kresd +capng_name = get_option('capng') == 'disabled' ? '' : 'libcap-ng' +capng = dependency(capng_name, required: get_option('capng') == 'enabled') + ### sendmmsg has_sendmmsg = meson.get_compiler('c').has_function('sendmmsg', prefix: '#define _GNU_SOURCE\n#include ') @@ -103,6 +110,10 @@ sendmmsg = get_option('sendmmsg') == 'enabled' endif +### XDP: not configurable - we just check if libknot supports it +xdp = meson.get_compiler('c').has_header('libknot/xdp/xdp.h' + ) and libknot.version().version_compare('>= 3.0.2') + ### Systemd systemd_files = get_option('systemd_files') libsystemd = dependency('libsystemd', required: systemd_files == 'enabled') @@ -116,6 +127,7 @@ '-Wtype-limits', '-Wshadow', '-Werror=implicit-function-declaration', # Probably messed up includes; implicit functions are evil! + '-Werror=attributes', # Missing cleanup attribute could lead to memory leaks. '-fvisibility=hidden', '-DHAVE_ASPRINTF=' + have_asprintf.to_int().to_string(), @@ -123,7 +135,7 @@ # https://github.com/libuv/libuv/pull/2588/files#diff-04c6e90faac2675aa89e2176d2eec7d8 # https://github.com/libuv/libuv/issues/1230#issuecomment-569030944 # Performance impact in our case seems OK: - # https://gitlab.labs.nic.cz/knot/knot-resolver/-/merge_requests/962#note_147407 + # https://gitlab.nic.cz/knot/knot-resolver/-/merge_requests/962#note_147407 '-fno-strict-aliasing', language: 'c', @@ -160,11 +172,12 @@ libzscanner.get_pkgconfig_variable('soname')) conf_data.set_quoted('libknot_SONAME', libknot.get_pkgconfig_variable('soname')) -conf_data.set('SYSTEMD_VERSION', - libsystemd.found() ? libsystemd.version().to_int() : -1) +conf_data.set('ENABLE_LIBSYSTEMD', libsystemd.found().to_int()) conf_data.set('NOVERBOSELOG', not verbose_log) conf_data.set('ENABLE_SENDMMSG', sendmmsg.to_int()) -conf_data.set('ENABLE_CAP_NG', capng.found()) +conf_data.set('ENABLE_XDP', xdp.to_int()) +conf_data.set('ENABLE_CAP_NG', capng.found().to_int()) +conf_data.set('ENABLE_DOH2', nghttp2.found().to_int()) kresconfig = configure_file( output: 'kresconfig.h', @@ -254,7 +267,7 @@ command: [ flake8, '--max-line-length=100', - join_paths(meson.source_root(), 'tests', 'pytests'), + meson.source_root() / 'tests' / 'pytests', ], ) endif @@ -278,7 +291,10 @@ s_build_extra_tests = build_extra_tests ? 'enabled' : 'disabled' s_install_kresd_conf = install_kresd_conf ? 'enabled' : 'disabled' s_sendmmsg = sendmmsg ? 'enabled': 'disabled' +s_xdp = xdp ? 'enabled': 'disabled' s_openssl = openssl.found() ? 'present': 'missing' +s_capng = capng.found() ? 'enabled': 'disabled' +s_doh2 = nghttp2.found() ? 'enabled': 'disabled' message(''' ======================= SUMMARY ======================= @@ -313,7 +329,10 @@ group: @0@'''.format(group) + ''' install_kresd_conf: @0@'''.format(s_install_kresd_conf) + ''' sendmmsg: @0@'''.format(s_sendmmsg) + ''' + XDP (in libknot): @0@'''.format(s_xdp) + ''' openssl debug: @0@'''.format(s_openssl) + ''' + capng: @0@'''.format(s_capng) + ''' + doh2: @0@'''.format(s_doh2) + ''' ======================================================= diff -Nru knot-resolver-5.1.1/meson_options.txt knot-resolver-5.2.1/meson_options.txt --- knot-resolver-5.1.1/meson_options.txt 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/meson_options.txt 2020-12-09 09:44:29.000000000 +0000 @@ -89,6 +89,18 @@ description: 'use sendmmsg syscall towards clients', ) +option( + 'capng', + type: 'combo', + choices: [ + 'auto', + 'enabled', + 'disabled', + ], + value: 'auto', + description: 'use libcapng to drop capabilities for non-root users', +) + ## Systemd option( 'systemd_files', diff -Nru knot-resolver-5.1.1/modules/bogus_log/meson.build knot-resolver-5.2.1/modules/bogus_log/meson.build --- knot-resolver-5.1.1/modules/bogus_log/meson.build 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/bogus_log/meson.build 2020-12-09 09:44:29.000000000 +0000 @@ -16,5 +16,5 @@ ) integr_tests += [ - ['bogus_log', join_paths(meson.current_source_dir(), 'test.integr')], + ['bogus_log', meson.current_source_dir() / 'test.integr'], ] diff -Nru knot-resolver-5.1.1/modules/bogus_log/packaging/debian/10/rundeps knot-resolver-5.2.1/modules/bogus_log/packaging/debian/10/rundeps --- knot-resolver-5.1.1/modules/bogus_log/packaging/debian/10/rundeps 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/bogus_log/packaging/debian/10/rundeps 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -lua-http diff -Nru knot-resolver-5.1.1/modules/bogus_log/packaging/test.config knot-resolver-5.2.1/modules/bogus_log/packaging/test.config --- knot-resolver-5.1.1/modules/bogus_log/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/bogus_log/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('bogus_log') -assert(bogus_log) -quit() diff -Nru knot-resolver-5.1.1/modules/bogus_log/.packaging/test.config knot-resolver-5.2.1/modules/bogus_log/.packaging/test.config --- knot-resolver-5.1.1/modules/bogus_log/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/bogus_log/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('bogus_log') +assert(bogus_log) +quit() diff -Nru knot-resolver-5.1.1/modules/bogus_log/test.integr/deckard.yaml knot-resolver-5.2.1/modules/bogus_log/test.integr/deckard.yaml --- knot-resolver-5.1.1/modules/bogus_log/test.integr/deckard.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/bogus_log/test.integr/deckard.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -3,8 +3,7 @@ - name: kresd binary: kresd additional: - - -f - - "1" + - --noninteractive templates: - modules/bogus_log/test.integr/kresd_config.j2 - tests/integration/hints_zone.j2 diff -Nru knot-resolver-5.1.1/modules/cookies/cookiemonster.c knot-resolver-5.2.1/modules/cookies/cookiemonster.c --- knot-resolver-5.1.1/modules/cookies/cookiemonster.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/cookies/cookiemonster.c 2020-12-09 09:44:29.000000000 +0000 @@ -343,7 +343,7 @@ return ctx->state; } - knot_pkt_t *answer = req->answer; + knot_pkt_t *answer = req->answer; // FIXME: see kr_request_ensure_answer() if (ctx->state & (KR_STATE_DONE | KR_STATE_FAIL)) { return ctx->state; diff -Nru knot-resolver-5.1.1/modules/daf/daf_http.test.lua knot-resolver-5.2.1/modules/daf/daf_http.test.lua --- knot-resolver-5.1.1/modules/daf/daf_http.test.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/daf/daf_http.test.lua 2020-12-09 09:44:29.000000000 +0000 @@ -2,9 +2,13 @@ -- check prerequisites local has_http = pcall(require, 'kres_modules.http') and pcall(require, 'http.request') if not has_http then - pass('skipping daf module test because http its not installed') - done() + -- skipping daf module test because http its not installed + os.exit(77) else + local path = worker.cwd..'/control/'..worker.pid + same(true, net.listen(path, nil, {kind = 'control'}), + 'new control sockets were created so map() can work') + local request = require('http.request') modules.load('http') diff -Nru knot-resolver-5.1.1/modules/daf/daf.lua knot-resolver-5.2.1/modules/daf/daf.lua --- knot-resolver-5.1.1/modules/daf/daf.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/daf/daf.lua 2020-12-09 09:44:29.000000000 +0000 @@ -191,6 +191,7 @@ return true end end + return nil end -- @function Find a rule @@ -200,6 +201,7 @@ return r end end + return nil end -- @function Enable/disable a rule @@ -210,6 +212,7 @@ return true end end + return nil end -- @function Enable/disable a rule @@ -221,14 +224,10 @@ end local function consensus(op, ...) - local ret = false local results = map(string.format(op, ...)) - for idx, r in ipairs(results) do - if idx == 1 then - -- non-empty table, init to true - ret = true - end - ret = ret and r + local ret = results.n > 0 -- init to true for non-empty results + for idx=1, results.n do + ret = ret and results[idx] end return ret end @@ -270,8 +269,9 @@ if query then local ok, r = pcall(M.add, query) if not ok then return 500, string.format('"%s"', r:match('/([^/]+)$')) end - -- Dispatch to all other workers - consensus('daf.add "%s"', query) + -- Dispatch to all other workers: + -- we ignore return values except error() because they are not serializable + consensus('daf.add "%s" and true', query) return rule_info(r) end return 400 @@ -298,11 +298,15 @@ local function getmatches() local update = {} - for _, rules in ipairs(map 'daf.rules') do - for _, r in ipairs(rules) do - local id = tostring(r.rule.id) - -- Must have string keys for JSON object and not an array - update[id] = (update[id] or 0) + r.rule.count + -- Must have string keys for JSON object and not an array + local inst_counters = map('ret = {} ' + .. 'for _, rule in ipairs(daf.rules) do ' + .. 'ret[tostring(rule.rule.id)] = rule.rule.count ' + .. 'end ' + .. 'return ret') + for inst_idx=1, inst_counters.n do + for r_id, r_cnt in pairs(inst_counters[inst_idx]) do + update[r_id] = (update[r_id] or 0) + r_cnt end end return update diff -Nru knot-resolver-5.1.1/modules/daf/daf.test.lua knot-resolver-5.2.1/modules/daf/daf.test.lua --- knot-resolver-5.1.1/modules/daf/daf.test.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/daf/daf.test.lua 2020-12-09 09:44:29.000000000 +0000 @@ -6,6 +6,10 @@ -- do not listen, test is driven by config code env.KRESD_NO_LISTEN = true +local path = worker.cwd..'/control/'..worker.pid +same(true, net.listen(path, nil, {kind = 'control'}), + 'new control sockets were created so map() can work') + modules.load('hints > iterate') modules.load('daf') @@ -17,18 +21,7 @@ hints['del2.'] = '127.0.0.1' hints['toggle.'] = '127.0.0.1' -local function check_answer(desc, qname, qtype, expected_rcode) - qtype_str = kres.tostring.type[qtype] - callback = function(pkt) - same(pkt:rcode(), expected_rcode, - desc .. ': expecting answer for query ' .. qname .. ' ' .. qtype_str - .. ' with rcode ' .. kres.tostring.rcode[expected_rcode]) - - ok((pkt:ancount() > 0) == (pkt:rcode() == kres.rcode.NOERROR), - desc ..': checking number of answers for ' .. qname .. ' ' .. qtype_str) - end - resolve(qname, qtype, kres.class.IN, {}, callback) -end +local check_answer = require('test_utils').check_answer local function test_sanity() check_answer('daf sanity (no rules)', 'pass.', kres.type.A, kres.rcode.NOERROR) diff -Nru knot-resolver-5.1.1/modules/daf/meson.build knot-resolver-5.2.1/modules/daf/meson.build --- knot-resolver-5.1.1/modules/daf/meson.build 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/daf/meson.build 2020-12-09 09:44:29.000000000 +0000 @@ -7,7 +7,7 @@ ] integr_tests += [ - ['daf', join_paths(meson.current_source_dir(), 'test.integr')], + ['daf', meson.current_source_dir() / 'test.integr'], ] lua_mod_src += [ @@ -17,5 +17,5 @@ # install daf.js install_data( 'daf.js', - install_dir: join_paths(modules_dir, 'daf'), + install_dir: modules_dir / 'daf', ) diff -Nru knot-resolver-5.1.1/modules/daf/packaging/test.config knot-resolver-5.2.1/modules/daf/packaging/test.config --- knot-resolver-5.1.1/modules/daf/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/daf/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('daf') -assert(daf) -quit() diff -Nru knot-resolver-5.1.1/modules/daf/.packaging/test.config knot-resolver-5.2.1/modules/daf/.packaging/test.config --- knot-resolver-5.1.1/modules/daf/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/daf/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('daf') +assert(daf) +quit() diff -Nru knot-resolver-5.1.1/modules/daf/test.integr/deckard.yaml knot-resolver-5.2.1/modules/daf/test.integr/deckard.yaml --- knot-resolver-5.1.1/modules/daf/test.integr/deckard.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/daf/test.integr/deckard.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -3,8 +3,7 @@ - name: kresd binary: kresd additional: - - -f - - "1" + - --noninteractive templates: - modules/daf/test.integr/kresd_config.j2 - tests/integration/hints_zone.j2 diff -Nru knot-resolver-5.1.1/modules/detect_time_jump/packaging/test.config knot-resolver-5.2.1/modules/detect_time_jump/packaging/test.config --- knot-resolver-5.1.1/modules/detect_time_jump/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/detect_time_jump/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('detect_time_jump') -assert(detect_time_jump) -quit() diff -Nru knot-resolver-5.1.1/modules/detect_time_jump/.packaging/test.config knot-resolver-5.2.1/modules/detect_time_jump/.packaging/test.config --- knot-resolver-5.1.1/modules/detect_time_jump/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/detect_time_jump/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('detect_time_jump') +assert(detect_time_jump) +quit() diff -Nru knot-resolver-5.1.1/modules/detect_time_skew/detect_time_skew.lua knot-resolver-5.2.1/modules/detect_time_skew/detect_time_skew.lua --- knot-resolver-5.1.1/modules/detect_time_skew/detect_time_skew.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/detect_time_skew/detect_time_skew.lua 2020-12-09 09:44:29.000000000 +0000 @@ -9,7 +9,7 @@ -- Check time validity of RRSIGs in priming query -- luacheck: no unused args local function check_time_callback(pkt, req) - if pkt:rcode() ~= kres.rcode.NOERROR then + if pkt == nil or pkt:rcode() ~= kres.rcode.NOERROR then warn("[detect_time_skew] cannot resolve '.' NS") return nil end diff -Nru knot-resolver-5.1.1/modules/detect_time_skew/packaging/test.config knot-resolver-5.2.1/modules/detect_time_skew/packaging/test.config --- knot-resolver-5.1.1/modules/detect_time_skew/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/detect_time_skew/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('detect_time_skew') -assert(detect_time_skew) -quit() diff -Nru knot-resolver-5.1.1/modules/detect_time_skew/.packaging/test.config knot-resolver-5.2.1/modules/detect_time_skew/.packaging/test.config --- knot-resolver-5.1.1/modules/detect_time_skew/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/detect_time_skew/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('detect_time_skew') +assert(detect_time_skew) +quit() diff -Nru knot-resolver-5.1.1/modules/dns64/dns64.lua knot-resolver-5.2.1/modules/dns64/dns64.lua --- knot-resolver-5.1.1/modules/dns64/dns64.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/dns64/dns64.lua 2020-12-09 09:44:29.000000000 +0000 @@ -31,7 +31,7 @@ local qry = req:current() -- Observe only final answers in IN class where request has no CD flag. if M.proxy == nil or not qry.flags.RESOLVED - or pkt:qclass() ~= kres.class.IN or req.answer:cd() then + or pkt:qclass() ~= kres.class.IN or req.qsource.packet:cd() then return state end -- Synthetic AAAA from marked A responses diff -Nru knot-resolver-5.1.1/modules/dns64/packaging/test.config knot-resolver-5.2.1/modules/dns64/packaging/test.config --- knot-resolver-5.1.1/modules/dns64/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/dns64/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('dns64') -assert(dns64) -quit() diff -Nru knot-resolver-5.1.1/modules/dns64/.packaging/test.config knot-resolver-5.2.1/modules/dns64/.packaging/test.config --- knot-resolver-5.1.1/modules/dns64/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dns64/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('dns64') +assert(dns64) +quit() diff -Nru knot-resolver-5.1.1/modules/dnstap/dnstap.c knot-resolver-5.2.1/modules/dnstap/dnstap.c --- knot-resolver-5.1.1/modules/dnstap/dnstap.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/dnstap.c 2020-12-09 09:44:29.000000000 +0000 @@ -33,7 +33,7 @@ /* * dt_pack packs the dnstap message for transport - * https://gitlab.labs.nic.cz/knot/knot-dns/blob/master/src/contrib/dnstap/dnstap.c#L24 + * https://gitlab.nic.cz/knot/knot-dns/blob/master/src/contrib/dnstap/dnstap.c#L24 * */ uint8_t* dt_pack(const Dnstap__Dnstap *d, uint8_t **buf, size_t *sz) { @@ -54,7 +54,7 @@ } /* set_address fills in address detail in dnstap_message - * https://gitlab.labs.nic.cz/knot/knot-dns/blob/master/src/contrib/dnstap/message.c#L28 + * https://gitlab.nic.cz/knot/knot-dns/blob/master/src/contrib/dnstap/message.c#L28 */ static void set_address(const struct sockaddr *sockaddr, ProtobufCBinaryData *addr, @@ -136,9 +136,11 @@ if (dnstap_dt->log_resp_pkt) { const knot_pkt_t *rpkt = req->answer; - m.response_message.len = rpkt->size; - m.response_message.data = (uint8_t *)rpkt->wire; - m.has_response_message = true; + m.has_response_message = rpkt != NULL; + if (rpkt != NULL) { + m.response_message.len = rpkt->size; + m.response_message.data = (uint8_t *)rpkt->wire; + } } /* set query time to the timestamp of the first kr_query @@ -232,7 +234,7 @@ } /* dnstap_unix_writer returns a unix fstream writer - * https://gitlab.labs.nic.cz/knot/knot-dns/blob/master/src/knot/modules/dnstap.c#L159 + * https://gitlab.nic.cz/knot/knot-dns/blob/master/src/knot/modules/dnstap.c#L159 */ static struct fstrm_writer* dnstap_unix_writer(const char *path) { diff -Nru knot-resolver-5.1.1/modules/dnstap/meson.build knot-resolver-5.2.1/modules/dnstap/meson.build --- knot-resolver-5.1.1/modules/dnstap/meson.build 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/meson.build 2020-12-09 09:44:29.000000000 +0000 @@ -30,7 +30,7 @@ protoc_c, '--c_out=' + meson.current_build_dir(), '--proto_path', meson.current_source_dir(), - join_paths([meson.current_source_dir(), 'dnstap.proto']), + meson.current_source_dir() / 'dnstap.proto', ], output: [ 'dnstap.pb-c.h', diff -Nru knot-resolver-5.1.1/modules/dnstap/packaging/debian/10/builddeps knot-resolver-5.2.1/modules/dnstap/packaging/debian/10/builddeps --- knot-resolver-5.1.1/modules/dnstap/packaging/debian/10/builddeps 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/packaging/debian/10/builddeps 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -libfstrm-dev -libprotobuf-c-dev -protobuf-c-compiler diff -Nru knot-resolver-5.1.1/modules/dnstap/packaging/debian/10/rundeps knot-resolver-5.2.1/modules/dnstap/packaging/debian/10/rundeps --- knot-resolver-5.1.1/modules/dnstap/packaging/debian/10/rundeps 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/packaging/debian/10/rundeps 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -libfstrm0 -libprotobuf-c1 diff -Nru knot-resolver-5.1.1/modules/dnstap/packaging/test.config knot-resolver-5.2.1/modules/dnstap/packaging/test.config --- knot-resolver-5.1.1/modules/dnstap/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('dnstap') -assert(dnstap) -quit() diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/centos/7/builddeps knot-resolver-5.2.1/modules/dnstap/.packaging/centos/7/builddeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/centos/7/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/centos/7/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +fstrm-devel +protobuf-c-devel +protobuf-c-compiler diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/centos/7/rundeps knot-resolver-5.2.1/modules/dnstap/.packaging/centos/7/rundeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/centos/7/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/centos/7/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,2 @@ +fstrm +protobuf-c diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/centos/8/builddeps knot-resolver-5.2.1/modules/dnstap/.packaging/centos/8/builddeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/centos/8/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/centos/8/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +fstrm-devel +protobuf-c-devel +protobuf-c-compiler diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/centos/8/rundeps knot-resolver-5.2.1/modules/dnstap/.packaging/centos/8/rundeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/centos/8/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/centos/8/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,2 @@ +fstrm +protobuf-c diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/debian/10/builddeps knot-resolver-5.2.1/modules/dnstap/.packaging/debian/10/builddeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/debian/10/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/debian/10/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +libfstrm-dev +libprotobuf-c-dev +protobuf-c-compiler diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/debian/10/rundeps knot-resolver-5.2.1/modules/dnstap/.packaging/debian/10/rundeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/debian/10/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/debian/10/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,2 @@ +libfstrm0 +libprotobuf-c1 diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/debian/9/builddeps knot-resolver-5.2.1/modules/dnstap/.packaging/debian/9/builddeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/debian/9/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/debian/9/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +libfstrm-dev +libprotobuf-c-dev +protobuf-c-compiler diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/debian/9/rundeps knot-resolver-5.2.1/modules/dnstap/.packaging/debian/9/rundeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/debian/9/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/debian/9/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,2 @@ +libfstrm0 +libprotobuf-c1 diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/fedora/31/builddeps knot-resolver-5.2.1/modules/dnstap/.packaging/fedora/31/builddeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/fedora/31/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/fedora/31/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +fstrm-devel +protobuf-c-devel +protobuf-c-compiler diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/fedora/31/rundeps knot-resolver-5.2.1/modules/dnstap/.packaging/fedora/31/rundeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/fedora/31/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/fedora/31/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,2 @@ +fstrm +protobuf-c diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/fedora/32/builddeps knot-resolver-5.2.1/modules/dnstap/.packaging/fedora/32/builddeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/fedora/32/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/fedora/32/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +fstrm-devel +protobuf-c-devel +protobuf-c-compiler diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/fedora/32/rundeps knot-resolver-5.2.1/modules/dnstap/.packaging/fedora/32/rundeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/fedora/32/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/fedora/32/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,2 @@ +fstrm +protobuf-c diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/leap/15.2/builddeps knot-resolver-5.2.1/modules/dnstap/.packaging/leap/15.2/builddeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/leap/15.2/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/leap/15.2/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +fstrm-devel +libprotobuf-c-devel +protobuf-c diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/leap/15.2/rundeps knot-resolver-5.2.1/modules/dnstap/.packaging/leap/15.2/rundeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/leap/15.2/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/leap/15.2/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,2 @@ +fstrm +protobuf-c diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/test.config knot-resolver-5.2.1/modules/dnstap/.packaging/test.config --- knot-resolver-5.1.1/modules/dnstap/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('dnstap') +assert(dnstap) +quit() diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/ubuntu/16.04/builddeps knot-resolver-5.2.1/modules/dnstap/.packaging/ubuntu/16.04/builddeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/ubuntu/16.04/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/ubuntu/16.04/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +libfstrm-dev +libprotobuf-c-dev +protobuf-c-compiler diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/ubuntu/16.04/rundeps knot-resolver-5.2.1/modules/dnstap/.packaging/ubuntu/16.04/rundeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/ubuntu/16.04/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/ubuntu/16.04/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,2 @@ +libfstrm0 +libprotobuf-c1 diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/ubuntu/18.04/builddeps knot-resolver-5.2.1/modules/dnstap/.packaging/ubuntu/18.04/builddeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/ubuntu/18.04/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/ubuntu/18.04/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +libfstrm-dev +libprotobuf-c-dev +protobuf-c-compiler diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/ubuntu/18.04/rundeps knot-resolver-5.2.1/modules/dnstap/.packaging/ubuntu/18.04/rundeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/ubuntu/18.04/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/ubuntu/18.04/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,2 @@ +libfstrm0 +libprotobuf-c1 diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/ubuntu/20.04/builddeps knot-resolver-5.2.1/modules/dnstap/.packaging/ubuntu/20.04/builddeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/ubuntu/20.04/builddeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/ubuntu/20.04/builddeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +libfstrm-dev +libprotobuf-c-dev +protobuf-c-compiler diff -Nru knot-resolver-5.1.1/modules/dnstap/.packaging/ubuntu/20.04/rundeps knot-resolver-5.2.1/modules/dnstap/.packaging/ubuntu/20.04/rundeps --- knot-resolver-5.1.1/modules/dnstap/.packaging/ubuntu/20.04/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/dnstap/.packaging/ubuntu/20.04/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,2 @@ +libfstrm0 +libprotobuf-c1 diff -Nru knot-resolver-5.1.1/modules/edns_keepalive/packaging/test.config knot-resolver-5.2.1/modules/edns_keepalive/packaging/test.config --- knot-resolver-5.1.1/modules/edns_keepalive/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/edns_keepalive/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('edns_keepalive') - -for _,item in ipairs(modules.list()) do - if item == "edns_keepalive" then - os.exit(0) - end -end - -os.exit(1) diff -Nru knot-resolver-5.1.1/modules/edns_keepalive/.packaging/test.config knot-resolver-5.2.1/modules/edns_keepalive/.packaging/test.config --- knot-resolver-5.1.1/modules/edns_keepalive/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/edns_keepalive/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,10 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('edns_keepalive') + +for _,item in ipairs(modules.list()) do + if item == "edns_keepalive" then + os.exit(0) + end +end + +os.exit(1) diff -Nru knot-resolver-5.1.1/modules/etcd/packaging/debian/10/builddeps knot-resolver-5.2.1/modules/etcd/packaging/debian/10/builddeps --- knot-resolver-5.1.1/modules/etcd/packaging/debian/10/builddeps 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/packaging/debian/10/builddeps 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -libssl-dev -luarocks -git diff -Nru knot-resolver-5.1.1/modules/etcd/packaging/debian/10/install.sh knot-resolver-5.2.1/modules/etcd/packaging/debian/10/install.sh --- knot-resolver-5.1.1/modules/etcd/packaging/debian/10/install.sh 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/packaging/debian/10/install.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -luarocks install etcd --from=https://mah0x211.github.io/rocks/ diff -Nru knot-resolver-5.1.1/modules/etcd/packaging/test.config knot-resolver-5.2.1/modules/etcd/packaging/test.config --- knot-resolver-5.1.1/modules/etcd/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('etcd') -assert(etcd) -quit() diff -Nru knot-resolver-5.1.1/modules/etcd/.packaging/centos/7/pre-test.sh knot-resolver-5.2.1/modules/etcd/.packaging/centos/7/pre-test.sh --- knot-resolver-5.1.1/modules/etcd/.packaging/centos/7/pre-test.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/.packaging/centos/7/pre-test.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +luarocks install etcd --from=https://mah0x211.github.io/rocks/ diff -Nru knot-resolver-5.1.1/modules/etcd/.packaging/centos/7/rundeps knot-resolver-5.2.1/modules/etcd/.packaging/centos/7/rundeps --- knot-resolver-5.1.1/modules/etcd/.packaging/centos/7/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/.packaging/centos/7/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,6 @@ +openssl-devel +lua-devel +luarocks +git +gcc +make diff -Nru knot-resolver-5.1.1/modules/etcd/.packaging/debian/10/pre-test.sh knot-resolver-5.2.1/modules/etcd/.packaging/debian/10/pre-test.sh --- knot-resolver-5.1.1/modules/etcd/.packaging/debian/10/pre-test.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/.packaging/debian/10/pre-test.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +luarocks --lua-version 5.1 install etcd --from=https://mah0x211.github.io/rocks/ diff -Nru knot-resolver-5.1.1/modules/etcd/.packaging/debian/10/rundeps knot-resolver-5.2.1/modules/etcd/.packaging/debian/10/rundeps --- knot-resolver-5.1.1/modules/etcd/.packaging/debian/10/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/.packaging/debian/10/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +libssl-dev +luarocks +git +make diff -Nru knot-resolver-5.1.1/modules/etcd/.packaging/debian/9/pre-test.sh knot-resolver-5.2.1/modules/etcd/.packaging/debian/9/pre-test.sh --- knot-resolver-5.1.1/modules/etcd/.packaging/debian/9/pre-test.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/.packaging/debian/9/pre-test.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +luarocks install etcd --from=https://mah0x211.github.io/rocks/ diff -Nru knot-resolver-5.1.1/modules/etcd/.packaging/debian/9/rundeps knot-resolver-5.2.1/modules/etcd/.packaging/debian/9/rundeps --- knot-resolver-5.1.1/modules/etcd/.packaging/debian/9/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/.packaging/debian/9/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +libssl-dev +luarocks +git +make diff -Nru knot-resolver-5.1.1/modules/etcd/.packaging/fedora/31/NOTSUPPORTED knot-resolver-5.2.1/modules/etcd/.packaging/fedora/31/NOTSUPPORTED --- knot-resolver-5.1.1/modules/etcd/.packaging/fedora/31/NOTSUPPORTED 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/.packaging/fedora/31/NOTSUPPORTED 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,16 @@ +Error installing etcd using luarocks: + + + +Missing dependencies for process 1.9.0-1: + luarocks-fetch-gitrec >= 0.2 (not installed) + +process 1.9.0-1 depends on luarocks-fetch-gitrec >= 0.2 (not installed) +Installing https://luarocks.org/luarocks-fetch-gitrec-0.2-1.src.rock + +No existing manifest. Attempting to rebuild... +luarocks-fetch-gitrec 0.2-1 is now installed in /root/.luarocks (license: MIT) + + +Error: Unknown protocol gitrec + diff -Nru knot-resolver-5.1.1/modules/etcd/.packaging/fedora/32/NOTSUPPORTED knot-resolver-5.2.1/modules/etcd/.packaging/fedora/32/NOTSUPPORTED --- knot-resolver-5.1.1/modules/etcd/.packaging/fedora/32/NOTSUPPORTED 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/.packaging/fedora/32/NOTSUPPORTED 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,16 @@ +Error installing etcd using luarocks: + + + +Missing dependencies for process 1.9.0-1: + luarocks-fetch-gitrec >= 0.2 (not installed) + +process 1.9.0-1 depends on luarocks-fetch-gitrec >= 0.2 (not installed) +Installing https://luarocks.org/luarocks-fetch-gitrec-0.2-1.src.rock + +No existing manifest. Attempting to rebuild... +luarocks-fetch-gitrec 0.2-1 is now installed in /root/.luarocks (license: MIT) + + +Error: Unknown protocol gitrec + diff -Nru knot-resolver-5.1.1/modules/etcd/.packaging/leap/15.2/pre-test.sh knot-resolver-5.2.1/modules/etcd/.packaging/leap/15.2/pre-test.sh --- knot-resolver-5.1.1/modules/etcd/.packaging/leap/15.2/pre-test.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/.packaging/leap/15.2/pre-test.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +luarocks --lua-version 5.1 install etcd --from=https://mah0x211.github.io/rocks/ diff -Nru knot-resolver-5.1.1/modules/etcd/.packaging/leap/15.2/rundeps knot-resolver-5.2.1/modules/etcd/.packaging/leap/15.2/rundeps --- knot-resolver-5.1.1/modules/etcd/.packaging/leap/15.2/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/.packaging/leap/15.2/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,6 @@ +libopenssl-devel +lua51-devel +lua51-luarocks +git +gcc +make diff -Nru knot-resolver-5.1.1/modules/etcd/.packaging/test.config knot-resolver-5.2.1/modules/etcd/.packaging/test.config --- knot-resolver-5.1.1/modules/etcd/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('etcd') +assert(etcd) +quit() diff -Nru knot-resolver-5.1.1/modules/etcd/.packaging/ubuntu/16.04/pre-test.sh knot-resolver-5.2.1/modules/etcd/.packaging/ubuntu/16.04/pre-test.sh --- knot-resolver-5.1.1/modules/etcd/.packaging/ubuntu/16.04/pre-test.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/.packaging/ubuntu/16.04/pre-test.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +luarocks install etcd --from=https://mah0x211.github.io/rocks/ diff -Nru knot-resolver-5.1.1/modules/etcd/.packaging/ubuntu/16.04/rundeps knot-resolver-5.2.1/modules/etcd/.packaging/ubuntu/16.04/rundeps --- knot-resolver-5.1.1/modules/etcd/.packaging/ubuntu/16.04/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/.packaging/ubuntu/16.04/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +libssl-dev +luarocks +git diff -Nru knot-resolver-5.1.1/modules/etcd/.packaging/ubuntu/18.04/pre-test.sh knot-resolver-5.2.1/modules/etcd/.packaging/ubuntu/18.04/pre-test.sh --- knot-resolver-5.1.1/modules/etcd/.packaging/ubuntu/18.04/pre-test.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/.packaging/ubuntu/18.04/pre-test.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +luarocks install etcd --from=https://mah0x211.github.io/rocks/ diff -Nru knot-resolver-5.1.1/modules/etcd/.packaging/ubuntu/18.04/rundeps knot-resolver-5.2.1/modules/etcd/.packaging/ubuntu/18.04/rundeps --- knot-resolver-5.1.1/modules/etcd/.packaging/ubuntu/18.04/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/.packaging/ubuntu/18.04/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +libssl-dev +luarocks +git diff -Nru knot-resolver-5.1.1/modules/etcd/.packaging/ubuntu/20.04/pre-test.sh knot-resolver-5.2.1/modules/etcd/.packaging/ubuntu/20.04/pre-test.sh --- knot-resolver-5.1.1/modules/etcd/.packaging/ubuntu/20.04/pre-test.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/.packaging/ubuntu/20.04/pre-test.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +luarocks --lua-version 5.1 install etcd --from=https://mah0x211.github.io/rocks/ diff -Nru knot-resolver-5.1.1/modules/etcd/.packaging/ubuntu/20.04/rundeps knot-resolver-5.2.1/modules/etcd/.packaging/ubuntu/20.04/rundeps --- knot-resolver-5.1.1/modules/etcd/.packaging/ubuntu/20.04/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/.packaging/ubuntu/20.04/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +libssl-dev +luarocks +git +make diff -Nru knot-resolver-5.1.1/modules/etcd/README.rst knot-resolver-5.2.1/modules/etcd/README.rst --- knot-resolver-5.1.1/modules/etcd/README.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/etcd/README.rst 2020-12-09 09:44:29.000000000 +0000 @@ -42,5 +42,5 @@ * `lua-etcd `_ library available in LuaRocks - ``$ luarocks install etcd --from=https://mah0x211.github.io/rocks/`` + ``$ luarocks --lua-version 5.1 install etcd --from=https://mah0x211.github.io/rocks/`` diff -Nru knot-resolver-5.1.1/modules/experimental_dot_auth/meson.build knot-resolver-5.2.1/modules/experimental_dot_auth/meson.build --- knot-resolver-5.1.1/modules/experimental_dot_auth/meson.build 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/experimental_dot_auth/meson.build 2020-12-09 09:44:29.000000000 +0000 @@ -9,5 +9,5 @@ install_subdir( 'static', strip_directory: true, - install_dir: join_paths(modules_dir, 'http'), + install_dir: modules_dir / 'http', ) diff -Nru knot-resolver-5.1.1/modules/experimental_dot_auth/packaging/debian/10/rundeps knot-resolver-5.2.1/modules/experimental_dot_auth/packaging/debian/10/rundeps --- knot-resolver-5.1.1/modules/experimental_dot_auth/packaging/debian/10/rundeps 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/experimental_dot_auth/packaging/debian/10/rundeps 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -lua-basexx diff -Nru knot-resolver-5.1.1/modules/experimental_dot_auth/packaging/test.config knot-resolver-5.2.1/modules/experimental_dot_auth/packaging/test.config --- knot-resolver-5.1.1/modules/experimental_dot_auth/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/experimental_dot_auth/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('experimental_dot_auth') -assert(experimental_dot_auth) -quit() diff -Nru knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/centos/7/rundeps knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/centos/7/rundeps --- knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/centos/7/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/centos/7/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua-basexx diff -Nru knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/centos/8/rundeps knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/centos/8/rundeps --- knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/centos/8/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/centos/8/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua5.1-basexx diff -Nru knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/debian/10/rundeps knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/debian/10/rundeps --- knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/debian/10/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/debian/10/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua-basexx diff -Nru knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/debian/9/rundeps knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/debian/9/rundeps --- knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/debian/9/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/debian/9/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua-basexx diff -Nru knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/fedora/31/rundeps knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/fedora/31/rundeps --- knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/fedora/31/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/fedora/31/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua5.1-basexx diff -Nru knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/fedora/32/rundeps knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/fedora/32/rundeps --- knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/fedora/32/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/fedora/32/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua5.1-basexx diff -Nru knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/leap/15.2/NOTSUPPORTED knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/leap/15.2/NOTSUPPORTED --- knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/leap/15.2/NOTSUPPORTED 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/leap/15.2/NOTSUPPORTED 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,6 @@ + +ERROR:test_packaging:Installing https://luarocks.org/basexx-0.4.1-1.rockspec +Error: Failed extracting v0.4.1.tar.gz + +Doesn't works on GitLab CI/CD, but works on localhost. +gzip and tar packages are installed, all packages has same version as packages on localhost's docker container. diff -Nru knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/leap/15.2/pre-test.sh knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/leap/15.2/pre-test.sh --- knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/leap/15.2/pre-test.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/leap/15.2/pre-test.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +luarocks --lua-version 5.1 install basexx --from=https://mah0x211.github.io/rocks/ diff -Nru knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/leap/15.2/rundeps knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/leap/15.2/rundeps --- knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/leap/15.2/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/leap/15.2/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +lua51-luarocks +git +tar +gzip diff -Nru knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/test.config knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/test.config --- knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('experimental_dot_auth') +assert(experimental_dot_auth) +quit() diff -Nru knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/ubuntu/18.04/rundeps knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/ubuntu/18.04/rundeps --- knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/ubuntu/18.04/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/ubuntu/18.04/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua-basexx diff -Nru knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/ubuntu/20.04/rundeps knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/ubuntu/20.04/rundeps --- knot-resolver-5.1.1/modules/experimental_dot_auth/.packaging/ubuntu/20.04/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/experimental_dot_auth/.packaging/ubuntu/20.04/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua-basexx diff -Nru knot-resolver-5.1.1/modules/graphite/graphite.lua knot-resolver-5.2.1/modules/graphite/graphite.lua --- knot-resolver-5.1.1/modules/graphite/graphite.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/graphite/graphite.lua 2020-12-09 09:44:29.000000000 +0000 @@ -3,18 +3,31 @@ if not stats then modules.load('stats') end -- This is leader-only module -if worker.id > 0 then return {} end local M = {} local socket = require("cqueues.socket") +local proto_txt = { + [socket.SOCK_DGRAM] = 'udp', + [socket.SOCK_STREAM] = 'tcp' +} local function make_socket(host, port, stype) local s, err, status + -- timeout before next interval begins (roughly) + local timeout_sec = (M.interval - 10) / sec s = socket.connect({ host = host, port = port, type = stype }) s:setmode('bn', 'bn') - status, err = pcall(s.connect, s) + s:settimeout(timeout_sec) + status, err = pcall(s.connect, s, timeout_sec) + if status == true and err == nil then + err = 'connect timeout' + s:close() + status = false + end if not status then + log('[graphite] connecting: %s@%d %s reason: %s', + host, port, proto_txt[stype], err) return status, err end return s @@ -30,38 +43,51 @@ return make_socket(host, port, socket.SOCK_STREAM) end -local function merge(results) - local t = {} - for _, result in ipairs(results) do - for k, v in pairs(result) do - t[k] = (t[k] or 0) + v - end - end - return t -end - -- Send the metrics in a table to multiple Graphite consumers local function publish_table(metrics, prefix, now) - for key,val in pairs(metrics) do - local msg = key..' '..val..' '..now..'\n' - if prefix then - msg = prefix..'.'..msg + local s + for i in ipairs(M.cli) do + local host = M.info[i] + + if M.cli[i] == -1 then + if host.tcp then + s = make_tcp(host.addr, host.port) + else + s = make_udp(host.addr, host.port) + end + if s then + M.cli[i] = s + end end - for i in ipairs(M.cli) do - local ok, err = M.cli[i]:write(msg) - if not ok then - -- Best-effort reconnect once per two tries - local tcp = M.cli[i]['connect'] ~= nil - local host = M.info[i] - if tcp and host.seen + 2 * M.interval / 1000 <= now then - print(string.format('[graphite] reconnecting: %s@%d reason: %s', - host.addr, host.port, err)) - M.cli[i] = make_tcp(host.addr, host.port) - host.seen = now + + if M.cli[i] ~= -1 then + for key,val in pairs(metrics) do + local msg = key..' '..val..' '..now..'\n' + if prefix then + msg = prefix..'.'..msg end - end + + local ok, err = pcall(M.cli[i].write, M.cli[i], msg) + if not ok then + local tcp = M.cli[i]['connect'] ~= nil + if tcp and host.seen + 2 * M.interval / 1000 <= now then + local sock_type = (host.tcp and socket.SOCK_STREAM) + or socket.SOCK_DGRAM + log('[graphite] reconnecting: %s@%d %s reason: %s', + host.addr, host.port, proto_txt[sock_type], err) + s = make_tcp(host.addr, host.port) + if s then + M.cli[i] = s + host.seen = now + else + M.cli[i] = -1 + break + end + end + end + end -- loop metrics end - end + end -- loop M.cli end function M.init() @@ -69,7 +95,7 @@ M.cli = {} M.info = {} M.interval = 5 * sec - M.prefix = 'kresd.' .. hostname() + M.prefix = string.format('kresd.%s.%s', hostname(), worker.id) return 0 end @@ -83,26 +109,17 @@ local now = os.time() -- Publish built-in statistics if not M.cli then error("no graphite server configured") end - publish_table(merge(map 'cache.stats()'), M.prefix..'.cache', now) - publish_table(merge(map 'worker.stats()'), M.prefix..'.worker', now) + publish_table(cache.stats(), M.prefix..'.cache', now) + publish_table(worker.stats(), M.prefix..'.worker', now) -- Publish extended statistics if available - publish_table(merge(map 'stats.list()'), M.prefix, now) + publish_table(stats.list(), M.prefix, now) return 0 end -- @function Make connection to Graphite server. function M.add_server(_, host, port, tcp) - local s, err - if tcp then - s, err = make_tcp(host, port) - else - s, err = make_udp(host, port) - end - if not s then - error(err) - end - table.insert(M.cli, s) - table.insert(M.info, {addr = host, port = port, seen = 0}) + table.insert(M.cli, -1) + table.insert(M.info, {addr = host, port = port, tcp = tcp, seen = 0}) return 0 end @@ -112,7 +129,6 @@ if not conf.port then conf.port = 2003 end if conf.interval then M.interval = conf.interval end if conf.prefix then M.prefix = conf.prefix end - -- connect to host(s) if type(conf.host) == 'table' then for _, val in pairs(conf.host) do M:add_server(val, conf.port, conf.tcp) @@ -122,7 +138,7 @@ end -- start publishing stats if M.ev then event.cancel(M.ev) end - M.ev = event.recurrent(M.interval, M.publish) + M.ev = event.recurrent(M.interval, function() worker.coroutine(M.publish) end) return 0 end diff -Nru knot-resolver-5.1.1/modules/graphite/packaging/debian/10/rundeps knot-resolver-5.2.1/modules/graphite/packaging/debian/10/rundeps --- knot-resolver-5.1.1/modules/graphite/packaging/debian/10/rundeps 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/graphite/packaging/debian/10/rundeps 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -lua-cqueues diff -Nru knot-resolver-5.1.1/modules/graphite/packaging/test.config knot-resolver-5.2.1/modules/graphite/packaging/test.config --- knot-resolver-5.1.1/modules/graphite/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/graphite/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('graphite') -assert(graphite) -quit() diff -Nru knot-resolver-5.1.1/modules/graphite/.packaging/centos/7/rundeps knot-resolver-5.2.1/modules/graphite/.packaging/centos/7/rundeps --- knot-resolver-5.1.1/modules/graphite/.packaging/centos/7/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/graphite/.packaging/centos/7/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua-cqueues diff -Nru knot-resolver-5.1.1/modules/graphite/.packaging/centos/8/rundeps knot-resolver-5.2.1/modules/graphite/.packaging/centos/8/rundeps --- knot-resolver-5.1.1/modules/graphite/.packaging/centos/8/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/graphite/.packaging/centos/8/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua5.1-cqueues diff -Nru knot-resolver-5.1.1/modules/graphite/.packaging/debian/10/rundeps knot-resolver-5.2.1/modules/graphite/.packaging/debian/10/rundeps --- knot-resolver-5.1.1/modules/graphite/.packaging/debian/10/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/graphite/.packaging/debian/10/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua-cqueues diff -Nru knot-resolver-5.1.1/modules/graphite/.packaging/debian/9/rundeps knot-resolver-5.2.1/modules/graphite/.packaging/debian/9/rundeps --- knot-resolver-5.1.1/modules/graphite/.packaging/debian/9/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/graphite/.packaging/debian/9/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua-cqueues diff -Nru knot-resolver-5.1.1/modules/graphite/.packaging/fedora/31/rundeps knot-resolver-5.2.1/modules/graphite/.packaging/fedora/31/rundeps --- knot-resolver-5.1.1/modules/graphite/.packaging/fedora/31/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/graphite/.packaging/fedora/31/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua5.1-cqueues diff -Nru knot-resolver-5.1.1/modules/graphite/.packaging/fedora/32/rundeps knot-resolver-5.2.1/modules/graphite/.packaging/fedora/32/rundeps --- knot-resolver-5.1.1/modules/graphite/.packaging/fedora/32/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/graphite/.packaging/fedora/32/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua5.1-cqueues diff -Nru knot-resolver-5.1.1/modules/graphite/.packaging/leap/15.2/NOTSUPPORTED knot-resolver-5.2.1/modules/graphite/.packaging/leap/15.2/NOTSUPPORTED --- knot-resolver-5.1.1/modules/graphite/.packaging/leap/15.2/NOTSUPPORTED 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/graphite/.packaging/leap/15.2/NOTSUPPORTED 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,6 @@ + +ERROR:test_packaging:Installing https://luarocks.org/cqueues-20190813.51-0.src.rock +164 Error: Failed extracting rel-20190813.tar.gz + +Doesn't works on GitLab CI/CD, but works on localhost. +gzip and tar packages are installed, all packages has same version as packages on localhost's docker container. diff -Nru knot-resolver-5.1.1/modules/graphite/.packaging/leap/15.2/pre-test.sh knot-resolver-5.2.1/modules/graphite/.packaging/leap/15.2/pre-test.sh --- knot-resolver-5.1.1/modules/graphite/.packaging/leap/15.2/pre-test.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/graphite/.packaging/leap/15.2/pre-test.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +luarocks --lua-version 5.1 install cqueues --from=https://mah0x211.github.io/rocks/ diff -Nru knot-resolver-5.1.1/modules/graphite/.packaging/leap/15.2/rundeps knot-resolver-5.2.1/modules/graphite/.packaging/leap/15.2/rundeps --- knot-resolver-5.1.1/modules/graphite/.packaging/leap/15.2/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/graphite/.packaging/leap/15.2/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,6 @@ +libopenssl-devel +lua51-luarocks +git +tar +gzip +m4 diff -Nru knot-resolver-5.1.1/modules/graphite/.packaging/test.config knot-resolver-5.2.1/modules/graphite/.packaging/test.config --- knot-resolver-5.1.1/modules/graphite/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/graphite/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('graphite') +assert(graphite) +quit() diff -Nru knot-resolver-5.1.1/modules/graphite/.packaging/ubuntu/16.04/rundeps knot-resolver-5.2.1/modules/graphite/.packaging/ubuntu/16.04/rundeps --- knot-resolver-5.1.1/modules/graphite/.packaging/ubuntu/16.04/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/graphite/.packaging/ubuntu/16.04/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua-cqueues diff -Nru knot-resolver-5.1.1/modules/graphite/.packaging/ubuntu/18.04/rundeps knot-resolver-5.2.1/modules/graphite/.packaging/ubuntu/18.04/rundeps --- knot-resolver-5.1.1/modules/graphite/.packaging/ubuntu/18.04/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/graphite/.packaging/ubuntu/18.04/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua-cqueues diff -Nru knot-resolver-5.1.1/modules/graphite/.packaging/ubuntu/20.04/rundeps knot-resolver-5.2.1/modules/graphite/.packaging/ubuntu/20.04/rundeps --- knot-resolver-5.1.1/modules/graphite/.packaging/ubuntu/20.04/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/graphite/.packaging/ubuntu/20.04/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua-cqueues diff -Nru knot-resolver-5.1.1/modules/graphite/README.rst knot-resolver-5.2.1/modules/graphite/README.rst --- knot-resolver-5.1.1/modules/graphite/README.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/graphite/README.rst 2020-12-09 09:44:29.000000000 +0000 @@ -19,11 +19,11 @@ modules = { graphite = { - prefix = hostname(), -- optional metric prefix + prefix = hostname() .. worker.id, -- optional metric prefix host = '127.0.0.1', -- graphite server address port = 2003, -- graphite server port interval = 5 * sec, -- publish interval - tcp = false -- set to true if want TCP mode + tcp = false -- set to true if you want TCP mode } } diff -Nru knot-resolver-5.1.1/modules/hints/hints.c knot-resolver-5.2.1/modules/hints/hints.c --- knot-resolver-5.1.1/modules/hints/hints.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/hints/hints.c 2020-12-09 09:44:29.000000000 +0000 @@ -106,10 +106,18 @@ knot_dname_t *qname = knot_dname_copy(qry->sname, &pkt->mm); knot_rrset_t rr; knot_rrset_init(&rr, qname, qry->stype, qry->sclass, data->ttl); - size_t family_len = sizeof(struct in_addr); - if (rr.type == KNOT_RRTYPE_AAAA) { + + size_t family_len; + switch (rr.type) { + case KNOT_RRTYPE_A: + family_len = sizeof(struct in_addr); + break; + case KNOT_RRTYPE_AAAA: family_len = sizeof(struct in6_addr); - } + break; + default: + goto finish; + }; /* Append address records from hints */ uint8_t *addr = pack_head(*addr_set); @@ -121,7 +129,7 @@ } addr = pack_obj_next(addr); } - +finish: return put_answer(pkt, qry, &rr, data->use_nodata); } @@ -137,20 +145,19 @@ if (!data) { /* No valid file. */ return ctx->state; } + /* We can optimize for early return like this: */ + if (!data->use_nodata && qry->stype != KNOT_RRTYPE_A + && qry->stype != KNOT_RRTYPE_AAAA && qry->stype != KNOT_RRTYPE_PTR) { + return ctx->state; + } /* FIXME: putting directly into packet breaks ordering in case the hint * is applied after a CNAME jump. */ - switch(qry->stype) { - case KNOT_RRTYPE_A: - case KNOT_RRTYPE_AAAA: /* Find forward record hints */ - if (satisfy_forward(data, pkt, qry) != 0) - return ctx->state; - break; - case KNOT_RRTYPE_PTR: /* Find PTR record */ + if (knot_dname_in_bailiwick(qry->sname, (const uint8_t *)"\4arpa\0") >= 0) { if (satisfy_reverse(data, pkt, qry) != 0) return ctx->state; - break; - default: - return ctx->state; /* Ignore */ + } else { + if (satisfy_forward(data, pkt, qry) != 0) + return ctx->state; } VERBOSE_MSG(qry, "<= answered from hints\n"); diff -Nru knot-resolver-5.1.1/modules/hints/packaging/test.config knot-resolver-5.2.1/modules/hints/packaging/test.config --- knot-resolver-5.1.1/modules/hints/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/hints/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('hints') -assert(hints) -quit() diff -Nru knot-resolver-5.1.1/modules/hints/.packaging/test.config knot-resolver-5.2.1/modules/hints/.packaging/test.config --- knot-resolver-5.1.1/modules/hints/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/hints/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('hints') +assert(hints) +quit() diff -Nru knot-resolver-5.1.1/modules/hints/tests/hints.test.lua knot-resolver-5.2.1/modules/hints/tests/hints.test.lua --- knot-resolver-5.1.1/modules/hints/tests/hints.test.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/hints/tests/hints.test.lua 2020-12-09 09:44:29.000000000 +0000 @@ -2,7 +2,7 @@ local utils = require('test_utils') -- setup resolver -modules = { 'hints' } +modules = { 'hints > iterate' } -- test for default configuration local function test_default() @@ -31,7 +31,34 @@ 'real IP address for a.root-servers.net. is correct') end +-- test that setting an address hint works (TODO: and NXDOMAIN) +local function test_nxdomain() + hints.config() -- clean start + hints.use_nodata(false) + hints['myname.lan'] = '192.0.2.1' + -- TODO: prefilling or some other way of getting NXDOMAIN (instead of SERVFAIL) + utils.check_answer('bad name gives NXDOMAIN', + 'badname.lan', kres.type.A, kres.rcode.SERVFAIL) + utils.check_answer('another type gives NXDOMAIN', + 'myname.lan', kres.type.AAAA, kres.rcode.SERVFAIL) + utils.check_answer('record itself is OK', + 'myname.lan', kres.type.A, kres.rcode.NOERROR) +end + +-- test that NODATA is correctly generated +local function test_nodata() + hints.config() -- clean start + hints.use_nodata(true) -- default ATM but let's not depend on that + hints['myname.lan'] = '2001:db8::1' + utils.check_answer('another type gives NODATA', + 'myname.lan', kres.type.MX, utils.NODATA) + utils.check_answer('record itself is OK', + 'myname.lan', kres.type.AAAA, kres.rcode.NOERROR) +end + return { test_default, - test_custom + test_custom, + test_nxdomain, + test_nodata, } diff -Nru knot-resolver-5.1.1/modules/http/http_doh.lua knot-resolver-5.2.1/modules/http/http_doh.lua --- knot-resolver-5.1.1/modules/http/http_doh.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/http_doh.lua 2020-12-09 09:44:29.000000000 +0000 @@ -16,10 +16,10 @@ if method == 'POST' then input = stream:get_body_chars(1025, 2) -- read timeout = KR_CONN_RTT_MAX elseif method == 'GET' then - local input_b64 = string.match(h:get(':path'), '^/doh%?dns=([a-zA-Z0-9_-]+)$') or - string.match(h:get(':path'), '^/doh%?dns=([a-zA-Z0-9_-]+)&') or - string.match(h:get(':path'), '^/doh%?.*&dns=([a-zA-Z0-9_-]+)$') or - string.match(h:get(':path'), '^/doh%?.*&dns=([a-zA-Z0-9_-]+)&') + local input_b64 = string.match(h:get(':path'), '^/[^?]*%?dns=([a-zA-Z0-9_-]+)$') or + string.match(h:get(':path'), '^/[^?]*%?dns=([a-zA-Z0-9_-]+)&') or + string.match(h:get(':path'), '^/[^?]*%?.*&dns=([a-zA-Z0-9_-]+)$') or + string.match(h:get(':path'), '^/[^?]*%?.*&dns=([a-zA-Z0-9_-]+)&') if not input_b64 then return 400, 'base64url query not found' end @@ -116,6 +116,7 @@ -- Export endpoints return { endpoints = { - ['/doh'] = {'text/plain', serve_doh, nil, nil, true}, + ['/doh'] = {'text/plain', serve_doh, nil, nil, true}, + ['/dns-query'] = {'text/plain', serve_doh, nil, nil, true}, } } diff -Nru knot-resolver-5.1.1/modules/http/http_doh.test.lua knot-resolver-5.2.1/modules/http/http_doh.test.lua --- knot-resolver-5.1.1/modules/http/http_doh.test.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/http_doh.test.lua 2020-12-09 09:44:29.000000000 +0000 @@ -3,7 +3,7 @@ local ffi = require('ffi') local function gen_huge_answer(_, req) - local answer = req.answer + local answer = req:ensure_answer() ffi.C.kr_pkt_make_auth_header(answer) answer:rcode(kres.rcode.NOERROR) @@ -19,7 +19,7 @@ local function gen_varying_ttls(_, req) local qry = req:current() - local answer = req.answer + local answer = req:ensure_answer() ffi.C.kr_pkt_make_auth_header(answer) answer:rcode(kres.rcode.NOERROR) @@ -75,8 +75,8 @@ -- check prerequisites local has_http = pcall(require, 'kres_modules.http') and pcall(require, 'http.request') if not has_http then - pass('skipping http module test because its not installed') - done() + -- skipping http module test because its not installed + os.exit(77) else policy.add(policy.suffix(policy.DROP, policy.todnames({'servfail.test.'}))) policy.add(policy.suffix(policy.DENY, policy.todnames({'nxdomain.test.'}))) @@ -360,6 +360,24 @@ modules.unload('view') end + local function test_dns_query_endpoint() + local desc = 'valid POST query which ends with SERVFAIL on /dns-query' + local request = require('http.request') + uri_templ = string.format('http://%s:%d/dns-query', host, port) + req = assert(request.new_from_uri(uri_templ)) + req.headers:upsert('content-type', 'application/dns-message') + req.headers:upsert(':method', 'POST') + req:set_body(basexx.from_base64( -- servfail.test. A + 'FZUBAAABAAAAAAAACHNlcnZmYWlsBHRlc3QAAAEAAQ==')) + local headers, pkt = check_ok(req, desc) + if not (headers and pkt) then + return + end + -- uncacheable + same(headers:get('cache-control'), 'max-age=0', desc .. ': TTL 0') + same(pkt:rcode(), kres.rcode.SERVFAIL, desc .. ': rcode matches') + end + -- not implemented -- local function test_post_unsupp_accept() -- local req = assert(req_templ:clone()) @@ -393,7 +411,8 @@ test_get_invalid_chars, test_unsupp_method, test_dstaddr, - test_srcaddr + test_srcaddr, + test_dns_query_endpoint, } return tests diff -Nru knot-resolver-5.1.1/modules/http/http.lua.in knot-resolver-5.2.1/modules/http/http.lua.in --- knot-resolver-5.1.1/modules/http/http.lua.in 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/http.lua.in 2020-12-09 09:44:29.000000000 +0000 @@ -347,10 +347,6 @@ -- @function Init module function M.init() - -- collect and merge metrics only on leader - if worker.id == 0 then - worker.coroutine(prometheus.init) - end net.register_endpoint_kind('doh', cb_socket) net.register_endpoint_kind('webmgmt', cb_socket) end @@ -360,7 +356,6 @@ for fd, _ in pairs(M.servers) do remove_socket(fd) end - prometheus.deinit() tls_cert.ephemeral_state_destroy(M.ephem_state) net.register_endpoint_kind('doh') net.register_endpoint_kind('webmgmt') diff -Nru knot-resolver-5.1.1/modules/http/http.test.lua knot-resolver-5.2.1/modules/http/http.test.lua --- knot-resolver-5.1.1/modules/http/http.test.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/http.test.lua 2020-12-09 09:44:29.000000000 +0000 @@ -2,9 +2,13 @@ -- check prerequisites local has_http = pcall(require, 'kres_modules.http') and pcall(require, 'http.request') if not has_http then - pass('skipping http module test because its not installed') - done() + -- skipping http module test because its not installed + os.exit(77) else + local path = worker.cwd..'/control/'..worker.pid + same(true, net.listen(path, nil, {kind = 'control'}), + 'new control sockets were created so map() can work') + local request = require('http.request') modules.load('http') diff -Nru knot-resolver-5.1.1/modules/http/meson.build knot-resolver-5.2.1/modules/http/meson.build --- knot-resolver-5.1.1/modules/http/meson.build 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/meson.build 2020-12-09 09:44:29.000000000 +0000 @@ -41,7 +41,7 @@ 'selectize.spdx', 'topojson.spdx', ], - install_dir: join_paths(modules_dir, 'http'), + install_dir: modules_dir / 'http', ) # auxiliary debug library for HTTP module diff -Nru knot-resolver-5.1.1/modules/http/packaging/debian/10/rundeps knot-resolver-5.2.1/modules/http/packaging/debian/10/rundeps --- knot-resolver-5.1.1/modules/http/packaging/debian/10/rundeps 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/packaging/debian/10/rundeps 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -lua-http diff -Nru knot-resolver-5.1.1/modules/http/packaging/test.config knot-resolver-5.2.1/modules/http/packaging/test.config --- knot-resolver-5.1.1/modules/http/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('http') -assert(http) -quit() diff -Nru knot-resolver-5.1.1/modules/http/.packaging/centos/7/rundeps knot-resolver-5.2.1/modules/http/.packaging/centos/7/rundeps --- knot-resolver-5.1.1/modules/http/.packaging/centos/7/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/.packaging/centos/7/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua-http diff -Nru knot-resolver-5.1.1/modules/http/.packaging/centos/8/rundeps knot-resolver-5.2.1/modules/http/.packaging/centos/8/rundeps --- knot-resolver-5.1.1/modules/http/.packaging/centos/8/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/.packaging/centos/8/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua5.1-http diff -Nru knot-resolver-5.1.1/modules/http/.packaging/debian/10/rundeps knot-resolver-5.2.1/modules/http/.packaging/debian/10/rundeps --- knot-resolver-5.1.1/modules/http/.packaging/debian/10/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/.packaging/debian/10/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua-http diff -Nru knot-resolver-5.1.1/modules/http/.packaging/debian/9/rundeps knot-resolver-5.2.1/modules/http/.packaging/debian/9/rundeps --- knot-resolver-5.1.1/modules/http/.packaging/debian/9/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/.packaging/debian/9/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua-http diff -Nru knot-resolver-5.1.1/modules/http/.packaging/fedora/31/rundeps knot-resolver-5.2.1/modules/http/.packaging/fedora/31/rundeps --- knot-resolver-5.1.1/modules/http/.packaging/fedora/31/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/.packaging/fedora/31/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua5.1-http diff -Nru knot-resolver-5.1.1/modules/http/.packaging/fedora/32/rundeps knot-resolver-5.2.1/modules/http/.packaging/fedora/32/rundeps --- knot-resolver-5.1.1/modules/http/.packaging/fedora/32/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/.packaging/fedora/32/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua5.1-http diff -Nru knot-resolver-5.1.1/modules/http/.packaging/leap/15.2/NOTSUPPORTED knot-resolver-5.2.1/modules/http/.packaging/leap/15.2/NOTSUPPORTED --- knot-resolver-5.1.1/modules/http/.packaging/leap/15.2/NOTSUPPORTED 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/.packaging/leap/15.2/NOTSUPPORTED 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,5 @@ + +https://github.com/wahern/luaossl/issues/175 + + +Doesn't work with libopenssl-devel 1.1.0i-lp151.1.1 diff -Nru knot-resolver-5.1.1/modules/http/.packaging/leap/15.2/pre-test.sh knot-resolver-5.2.1/modules/http/.packaging/leap/15.2/pre-test.sh --- knot-resolver-5.1.1/modules/http/.packaging/leap/15.2/pre-test.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/.packaging/leap/15.2/pre-test.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +luarocks --lua-version 5.1 install http --from=https://mah0x211.github.io/rocks/ diff -Nru knot-resolver-5.1.1/modules/http/.packaging/leap/15.2/rundeps knot-resolver-5.2.1/modules/http/.packaging/leap/15.2/rundeps --- knot-resolver-5.1.1/modules/http/.packaging/leap/15.2/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/.packaging/leap/15.2/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,7 @@ +libopenssl-devel +lua51-devel +lua51-luarocks +git +tar +gzip +m4 diff -Nru knot-resolver-5.1.1/modules/http/.packaging/test.config knot-resolver-5.2.1/modules/http/.packaging/test.config --- knot-resolver-5.1.1/modules/http/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('http') +assert(http) +quit() diff -Nru knot-resolver-5.1.1/modules/http/.packaging/ubuntu/18.04/rundeps knot-resolver-5.2.1/modules/http/.packaging/ubuntu/18.04/rundeps --- knot-resolver-5.1.1/modules/http/.packaging/ubuntu/18.04/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/.packaging/ubuntu/18.04/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua-http diff -Nru knot-resolver-5.1.1/modules/http/.packaging/ubuntu/20.04/rundeps knot-resolver-5.2.1/modules/http/.packaging/ubuntu/20.04/rundeps --- knot-resolver-5.1.1/modules/http/.packaging/ubuntu/20.04/rundeps 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/.packaging/ubuntu/20.04/rundeps 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1 @@ +lua-http diff -Nru knot-resolver-5.1.1/modules/http/prometheus.lua knot-resolver-5.2.1/modules/http/prometheus.lua --- knot-resolver-5.1.1/modules/http/prometheus.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/prometheus.lua 2020-12-09 09:44:29.000000000 +0000 @@ -5,8 +5,6 @@ finalize = function (_ --[[metrics]]) end, } -local snapshots, snapshots_count = {}, 120 - -- Gauge metrics local gauges = { ['worker.concurrent'] = true, @@ -32,89 +30,68 @@ return t end -local function snapshot_end() - snapshots_count = false -end - --- Function to sort frequency list -local function snapshot_start() - local prev = getstats() - while snapshots_count do - local is_empty = true - -- Get current snapshot - local cur, stats_dt = getstats(), {} - for k,v in pairs(cur) do - if gauges[k] then - stats_dt[k] = v - else - stats_dt[k] = v - (prev[k] or 0) - end - is_empty = is_empty and stats_dt[k] == 0 +-- @returns current stats + difference against previous data set passed in @param prev +local function snapshot_start(prev) + assert(type(prev) == 'table', 'table with previous values expected') + local is_empty = true + -- Get current snapshot + local cur, stats_dt = getstats(), {} + for k,v in pairs(cur) do + if gauges[k] then + stats_dt[k] = v + else + stats_dt[k] = v - (prev[k] or 0) end - prev = cur - -- Calculate upstreams and geotag them if possible - local upstreams - if http.geoip then - upstreams = stats.upstreams() - for k,v in pairs(upstreams) do - local gi - if string.find(k, '.', 1, true) then - gi = http.geoip:search_ipv4(k) - else - gi = http.geoip:search_ipv6(k) - end - if gi then - upstreams[k] = {data=v, location=gi.location, country=gi.country and gi.country.iso_code} - end + is_empty = is_empty and stats_dt[k] == 0 + end + -- Calculate upstreams and geotag them if possible + local upstreams + if http.geoip then + upstreams = stats.upstreams() + for k,v in pairs(upstreams) do + local gi + if string.find(k, '.', 1, true) then + gi = http.geoip:search_ipv4(k) + else + gi = http.geoip:search_ipv6(k) end - end - -- Aggregate per-worker metrics - local wdata = {} - for _, info in pairs(map 'worker.info()') do - if type(info) == 'table' then - wdata[tostring(info.pid)] = { - rss = info.rss, - usertime = info.usertime, - systime = info.systime, - pagefaults = info.pagefaults, - queries = info.queries - } + if gi then + upstreams[k] = {data=v, location=gi.location, country=gi.country and gi.country.iso_code} end end - -- Publish stats updates periodically - if not is_empty then - local update = {time=os.time(), stats=stats_dt, upstreams=upstreams, workers=wdata} - table.insert(snapshots, update) - if #snapshots > snapshots_count then - table.remove(snapshots, 1) - end + end + -- Aggregate per-worker metrics + local wdata = {} + for _, info in pairs(map 'worker.info()') do + if type(info) == 'table' then + wdata[tostring(info.pid)] = { + rss = info.rss, + usertime = info.usertime, + systime = info.systime, + pagefaults = info.pagefaults, + queries = info.queries + } end - worker.sleep(1) end + -- Publish stats updates periodically + if not is_empty then + local update = {time=os.time(), stats=stats_dt, upstreams=upstreams, workers=wdata} + return cur, update + end + return cur, nil end -- Function to sort frequency list local function stream_stats(_, ws) - -- Initially, stream history - local ok, last = true, nil - local batch = {} - for i, s in ipairs(snapshots) do - table.insert(batch, s) - if #batch == 20 or i + 1 == #snapshots then - ok = ws:send(tojson(batch)) - batch = {} - end - end + local ok = true -- Publish stats updates periodically + local prev = getstats() while ok do - -- Get last snapshot - local id = #snapshots - 1 - if id > 0 and snapshots[id].time ~= last then - local push = tojson(snapshots[id]) - last = snapshots[id].time - ok = ws:send(push) - end worker.sleep(1) + local update + prev, update = snapshot_start(prev) + local push = tojson(update) + ok = ws:send(push) end end @@ -160,8 +137,6 @@ end -- Export module interface -M.init = snapshot_start -M.deinit = snapshot_end M.endpoints = { ['/stats'] = {'application/json', getstats, stream_stats}, ['/frequent'] = {'application/json', function () return stats.frequent() end}, diff -Nru knot-resolver-5.1.1/modules/http/README.doh.rst knot-resolver-5.2.1/modules/http/README.doh.rst --- knot-resolver-5.1.1/modules/http/README.doh.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/README.doh.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,120 +0,0 @@ -.. SPDX-License-Identifier: GPL-3.0-or-later - -.. _mod-http-doh: - -DNS-over-HTTP (DoH) -=================== - -.. warning:: - - * DoH support was added in version 4.0.0 and is subject to change. - * DoH implementation in Knot Resolver is intended for experimentation - only as there is insufficient experience with the module - and the DoH protocol in general. - * For the time being it is recommended to run DoH endpoint - on a separate machine which is not handling normal DNS operations. - * Read about perceived benefits and risks at - `Mozilla's DoH page `_. - * It is important to understand **limits of encrypting only DNS traffic**. - Relevant security analysis can be found in article - *Simran Patil and Nikita Borisov. 2019. What can you learn from an IP?* - See `slides `_ - or `the article itself `_. - * Independent information about political controversies around the DoH - deployment by default can be found in blog posts - `DNS Privacy at IETF 104 `_ - and - `More DOH `_ - by Geoff Huston - and `Centralised DoH is bad for Privacy, in 2019 and beyond `_ - by Bert Hubert. - -Following section compares several options for running a DoH capable server. -Make sure you read through this chapter before exposing the DoH service to users. - -DoH support in Knot Resolver ----------------------------- - -The :ref:`HTTP module ` in Knot Resolver also provides support for -binary DNS-over-HTTP protocol standardized in :rfc:`8484`. - -This integrated DoH server has following properties: - -:Scenario: - HTTP module in Knot Resolver configured to provide ``/doh`` endpoint - (as shown below). - -:Advantages: - - Integrated solution provides management and monitoring in one place. - - Supports ACLs for DNS traffic based on client's IP address. - -:Disadvantages: - - Exposes Knot Resolver instance to attacks over HTTP. - - Does not offer fine grained authorization and logging at HTTP level. - - Let's Encrypt integration is not automated. - - -:ref:`Example configuration ` is part of examples for generic -HTTP module. After configuring your endpoint you can reach the DoH endpoint using -URL ``https://your.resolver.hostname.example/doh``, done! - -.. code-block:: bash - - # query for www.knot-resolver.cz AAAA - $ curl -k https://your.resolver.hostname.example/doh?dns=l1sBAAABAAAAAAAAA3d3dw1rbm90LXJlc29sdmVyAmN6AAAcAAE - -Please see section :ref:`mod-http-tls` for further details about TLS configuration. - -Alternative configurations use HTTP proxies between clients and a Knot Resolver instance: - -Normal HTTP proxy ------------------ -:Scenario: - A standard HTTP-compliant proxy is configured to proxy `GET` - and `POST` requests to HTTP endpoint `/doh` to a machine - running Knot Resolver. - -:Advantages: - - Protects Knot Resolver instance from - `some` types of attacks at HTTP level. - - Allows fine-grained filtering and logging at HTTP level. - - Let's Encrypt integration is readily available. - - Is based on mature software. - -:Disadvantages: - - Fine-grained ACLs for DNS traffic are not available because - proxy hides IP address of client sending DNS query. - - More complicated setup with two components (proxy + Knot Resolver). - -HTTP proxy with DoH support ---------------------------- -:Scenario: - HTTP proxy extended with a - `special module for DNS-over-HTTP `_. - The module transforms HTTP requests to standard DNS queries - which are then processed by Knot Resolver. - DNS replies from Knot Resolver are then transformed back to HTTP - encoding by the proxy. - -:Advantages: - - Protects Knot Resolver instance from `all` attacks at HTTP level. - - Allows fine-grained filtering and logging at HTTP level. - - Let's Encrypt integration is readily available - if proxy is based on a standard HTTP software. - -:Disadvantages: - - Fine-grained ACLs for DNS traffic are not available because - proxy hides IP address of client sending DNS query. - (Unless proxy and resolver are using non-standard packet extensions like - `DNS X-Proxied-For `_.) - - More complicated setup with three components (proxy + special module + Knot Resolver). - -Client configuration --------------------- -Most common client today is web browser Firefox, which requires manual configuration -to use your own DNS resolver. Configuration options in Firefox are described at -`Mozilla support site `_. - -.. warning:: - - Make sure you read :ref:`warnings at beginning of this section `. diff -Nru knot-resolver-5.1.1/modules/http/README.rst knot-resolver-5.2.1/modules/http/README.rst --- knot-resolver-5.1.1/modules/http/README.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/README.rst 2020-12-09 09:44:29.000000000 +0000 @@ -2,8 +2,8 @@ .. _mod-http: -HTTP services -============= +Other HTTP services +=================== .. tip:: In most distributions, the ``http`` module is available from a separate package ``knot-resolver-module-http``. The module isn't packaged @@ -14,18 +14,18 @@ modules to export restful APIs and websocket streams. One example is statistics module that can stream live metrics on the website, -or publish metrics on request for Prometheus scraper, and also :ref:`mod-http-doh`. +or publish metrics on request for Prometheus scraper. By default this module provides two kinds of endpoints, and unlimited number of "used-defined kinds" can be added in configuration. +--------------+---------------------------------------------------------------------------------+ -| **Endpoint** | **Explanation** | -+--------------+---------------------------------------------------------------------------------+ -| doh | :ref:`mod-http-doh` | +| **Kind** | **Explanation** | +--------------+---------------------------------------------------------------------------------+ | webmgmt | :ref:`built-in web management ` APIs (includes DoH) | +--------------+---------------------------------------------------------------------------------+ +| doh | :ref:`mod-http-doh` | ++--------------+---------------------------------------------------------------------------------+ Each network address and port combination can be configured to expose one kind of endpoint. This is done using the same mechanisms as @@ -124,6 +124,21 @@ they currently won't be shared. It's assumed that you don't want a self-signed certificate for serious deployments anyway. +.. _mod-http-doh: + +Legacy DNS-over-HTTPS (DoH) +--------------------------- + +.. warning:: The legacy DoH implementation using ``http`` module (``kind='doh'``) + is deprecated. It has known performance and stability issues that won't be fixed. + Use new :ref:`dns-over-https` implementation instead. + +This was an experimental implementation of :rfc:`8484`. It was configured using +``doh`` kind in :func:`net.listen`. Its configuration (such as certificates) +took place in ``http.config()``. + +Queries were served on ``/doh`` and ``/dns-query`` endpoints. + .. _mod-http-built-in-services: Built-in services @@ -137,7 +152,8 @@ "``/stats``", "Statistics/metrics", "Exported :ref:`metrics ` from :ref:`mod-stats` in JSON format." "``/metrics``", "Prometheus metrics", "Exported metrics for Prometheus_." "``/trace/:name/:type``", "Tracking", ":ref:`Trace resolution ` of a DNS query and return the verbose logs." - "``/doh``", "DNS-over-HTTP", ":rfc:`8484` endpoint, see :ref:`mod-http-doh`." + "``/doh``", "Legacy DNS-over-HTTPS", ":rfc:`8484` endpoint, see :ref:`mod-http-doh`." + "``/dns-query``", "Legacy DNS-over-HTTPS", ":rfc:`8484` endpoint, see :ref:`mod-http-doh`." Dependencies ------------ @@ -152,17 +168,17 @@ $ brew install openssl $ brew link openssl --force # Override system OpenSSL - Any other system can install from LuaRocks directly: + Some other systems can install from LuaRocks directly: .. code-block:: bash - $ luarocks install http + $ luarocks --lua-version 5.1 install http * (*optional*) `mmdblua `_ available in LuaRocks .. code-block:: bash - $ luarocks install --server=https://luarocks.org/dev mmdblua + $ luarocks --lua-version 5.1 install --server=https://luarocks.org/dev mmdblua $ curl -O https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz $ gzip -d GeoLite2-City.mmdb.gz diff -Nru knot-resolver-5.1.1/modules/http/test_tls/tls.test.lua knot-resolver-5.2.1/modules/http/test_tls/tls.test.lua --- knot-resolver-5.1.1/modules/http/test_tls/tls.test.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/http/test_tls/tls.test.lua 2020-12-09 09:44:29.000000000 +0000 @@ -2,8 +2,8 @@ -- check prerequisites local has_http = pcall(require, 'kres_modules.http') and pcall(require, 'http.request') if not has_http then - pass('skipping http module test because its not installed') - done() + -- skipping http module test because its not installed + os.exit(77) else local request = require('http.request') local openssl_ctx = require('openssl.ssl.context') diff -Nru knot-resolver-5.1.1/modules/meson.build knot-resolver-5.2.1/modules/meson.build --- knot-resolver-5.1.1/modules/meson.build 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/meson.build 2020-12-09 09:44:29.000000000 +0000 @@ -15,25 +15,23 @@ files('serve_stale/serve_stale.lua'), files('ta_sentinel/ta_sentinel.lua'), files('ta_signal_query/ta_signal_query.lua'), - files('ta_update/ta_update.lua'), files('watchdog/watchdog.lua'), files('workarounds/workarounds.lua'), ] # When adding tests, prefer to use module's meson.build (if it exists). config_tests += [ - ['predict', files('predict/predict.test.lua')], ['dns64', files('dns64/dns64.test.lua')], - ['ta_update', files('ta_update/ta_update.test.lua'), ['snowflake']], ['prefill', files('prefill/prefill.test/prefill.test.lua')], + ['renumber', files('renumber/renumber.test.lua')], + ['ta_update', files('ta_update/ta_update.test.lua'), ['snowflake']], ] integr_tests += [ - ['rebinding', join_paths(meson.current_source_dir(), 'rebinding', 'test.integr')], - ['serve_stale', join_paths(meson.current_source_dir(), 'serve_stale', 'test.integr')], + ['rebinding', meson.current_source_dir() / 'rebinding' / 'test.integr'], + ['serve_stale', meson.current_source_dir() / 'serve_stale' / 'test.integr'], # NOTE: ta_update may pass in cases when it should fail due to race conditions # To ensure reliability, deckard should introduce a time wait - ['ta_update', join_paths(meson.current_source_dir(), 'ta_update', 'ta_update.test.integr')], ] @@ -50,6 +48,7 @@ subdir('policy') subdir('refuse_nord') subdir('stats') +subdir('ta_update') subdir('view') # install lua modules diff -Nru knot-resolver-5.1.1/modules/nsid/packaging/test.config knot-resolver-5.2.1/modules/nsid/packaging/test.config --- knot-resolver-5.1.1/modules/nsid/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/nsid/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('nsid') -assert(nsid) -quit() diff -Nru knot-resolver-5.1.1/modules/nsid/.packaging/test.config knot-resolver-5.2.1/modules/nsid/.packaging/test.config --- knot-resolver-5.1.1/modules/nsid/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/nsid/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('nsid') +assert(nsid) +quit() diff -Nru knot-resolver-5.1.1/modules/policy/meson.build knot-resolver-5.2.1/modules/policy/meson.build --- knot-resolver-5.1.1/modules/policy/meson.build 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/policy/meson.build 2020-12-09 09:44:29.000000000 +0000 @@ -12,9 +12,9 @@ ] integr_tests += [ - ['policy', join_paths(meson.current_source_dir(), 'test.integr')], - ['policy.noipv6', join_paths(meson.current_source_dir(), 'noipv6.test.integr')], - ['policy.noipvx', join_paths(meson.current_source_dir(), 'noipvx.test.integr')], + ['policy', meson.current_source_dir() / 'test.integr'], + ['policy.noipv6', meson.current_source_dir() / 'noipv6.test.integr'], + ['policy.noipvx', meson.current_source_dir() / 'noipvx.test.integr'], ] # check git submodules were initialized diff -Nru knot-resolver-5.1.1/modules/policy/noipv6.test.integr/deckard.yaml knot-resolver-5.2.1/modules/policy/noipv6.test.integr/deckard.yaml --- knot-resolver-5.1.1/modules/policy/noipv6.test.integr/deckard.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/policy/noipv6.test.integr/deckard.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -3,8 +3,7 @@ - name: kresd binary: kresd additional: - - -f - - "1" + - --noninteractive templates: - modules/policy/noipv6.test.integr/kresd_config.j2 - tests/integration/hints_zone.j2 diff -Nru knot-resolver-5.1.1/modules/policy/noipvx.test.integr/deckard.yaml knot-resolver-5.2.1/modules/policy/noipvx.test.integr/deckard.yaml --- knot-resolver-5.1.1/modules/policy/noipvx.test.integr/deckard.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/policy/noipvx.test.integr/deckard.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -3,8 +3,7 @@ - name: kresd binary: kresd additional: - - -f - - "1" + - --noninteractive templates: - modules/policy/noipvx.test.integr/kresd_config.j2 - tests/integration/hints_zone.j2 diff -Nru knot-resolver-5.1.1/modules/policy/packaging/test.config knot-resolver-5.2.1/modules/policy/packaging/test.config --- knot-resolver-5.1.1/modules/policy/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/policy/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('policy') -assert(policy) -quit() diff -Nru knot-resolver-5.1.1/modules/policy/.packaging/test.config knot-resolver-5.2.1/modules/policy/.packaging/test.config --- knot-resolver-5.1.1/modules/policy/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/policy/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('policy') +assert(policy) +quit() diff -Nru knot-resolver-5.1.1/modules/policy/policy.lua knot-resolver-5.2.1/modules/policy/policy.lua --- knot-resolver-5.1.1/modules/policy/policy.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/policy/policy.lua 2020-12-09 09:44:29.000000000 +0000 @@ -213,7 +213,8 @@ function policy.ANSWER(rtable, nodata) return function(_, req) local qry = req:current() - local answer = req.answer + local answer = req:ensure_answer() + if answer == nil then return nil end local data = rtable[qry.stype] ffi.C.kr_pkt_make_auth_header(answer) @@ -228,7 +229,13 @@ answer:rcode(kres.rcode.NOERROR) answer:begin(kres.section.ANSWER) - answer:put(qry.sname, ttl, qry.sclass, qry.stype, data.rdata) + if type(data.rdata) == 'table' then + for _, rdato in ipairs(data.rdata) do + answer:put(qry.sname, ttl, qry.sclass, qry.stype, rdato) + end + else + answer:put(qry.sname, ttl, qry.sclass, qry.stype, data.rdata) + end return kres.DONE end @@ -248,7 +255,8 @@ -- Rule for localhost. zone; see RFC6303, sec. 3 local function localhost(_, req) local qry = req:current() - local answer = req.answer + local answer = req:ensure_answer() + if answer == nil then return nil end ffi.C.kr_pkt_make_auth_header(answer) local is_exact = ffi.C.knot_dname_is_equal(qry.sname, dname_localhost) @@ -280,7 +288,8 @@ -- TODO: much of this would better be left to the hints module (or coordinated). local function localhost_reversed(_, req) local qry = req:current() - local answer = req.answer + local answer = req:ensure_answer() + if answer == nil then return nil end -- classify qry.sname: local is_exact -- exact dname for localhost @@ -378,7 +387,6 @@ local function rpz_parse(action, path) local rules = {} local new_actions = {} - local origin = '\0' local action_map = { -- RPZ Policy Actions ['\0'] = action, @@ -388,22 +396,17 @@ ['\012rpz-tcp-only\0'] = policy.TC, -- Policy triggers @NYI@ } - local unsupp_rrs = function (rtype) - local set = { - kres.type.DNAME, - kres.type.NS, - kres.type.SOA, - kres.type.DNSKEY, - kres.type.DS, - kres.type.RRSIG, - kres.type.NSEC, - kres.type.NSEC3, - } - for _, l in pairs(set) do - if rtype == l then return true end - end - return false - end + -- RR types to be skipped; boolean denoting whether to throw a warning even for RPZ apex. + local rrtype_bad = { + [kres.type.DNAME] = true, + [kres.type.NS] = false, + [kres.type.SOA] = false, + [kres.type.DNSKEY] = true, + [kres.type.DS] = true, + [kres.type.RRSIG] = true, + [kres.type.NSEC] = true, + [kres.type.NSEC3] = true, + } local parser = require('zonefile').new() local ok, errstr = parser:open(path) if not ok then @@ -420,39 +423,47 @@ local rdata = ffi.string(parser.r_data, parser.r_data_length) ffi.C.knot_dname_to_lower(full_name) - if (parser.r_type == kres.type.SOA) then - origin = ffi.gc(ffi.C.knot_dname_copy(full_name, nil), ffi.C.free) + local prefix_labels = ffi.C.knot_dname_in_bailiwick(full_name, parser.zone_origin) + if prefix_labels < 0 then + log('[poli] RPZ %s:%d: RR owner "%s" outside the zone (ignored)', + path, tonumber(parser.line_counter), kres.dname2str(full_name)) goto continue end - local prefix_labels = ffi.C.knot_dname_in_bailiwick(full_name, origin) - local name - if prefix_labels > 0 then - local bytes = 0 - for _=1,prefix_labels do - bytes = bytes + 1 + full_name[bytes] - end - name = ffi.string(full_name, bytes) - name = name..'\0' - else - name = ffi.string(full_name, parser.r_owner_length) - end + local bytes = ffi.C.knot_dname_size(full_name) - ffi.C.knot_dname_size(parser.zone_origin) + local name = ffi.string(full_name, bytes) .. '\0' if parser.r_type == kres.type.CNAME then if action_map[rdata] then rules[name] = action_map[rdata] else - log('[poli] RPZ %s:%d: CNAME with custom target in RPZ is not supported', path, tonumber(parser.line_counter)) + log('[poli] RPZ %s:%d: CNAME with custom target in RPZ is not supported yet (ignored)', + path, tonumber(parser.line_counter)) end else - -- Warn when NYI if #name then - if unsupp_rrs(parser.r_type) then - log('[poli] RPZ %s:%d: RR type %s is not allowed in RPZ', path, tonumber(parser.line_counter), - kres.tostring.type[parser.r_type]) - else + local is_bad = rrtype_bad[parser.r_type] + if is_bad == true or (is_bad == false and prefix_labels ~= 0) then + log('[poli] RPZ %s:%d warning: RR type %s is not allowed in RPZ (ignored)', + path, tonumber(parser.line_counter), kres.tostring.type[parser.r_type]) + elseif is_bad == nil then if new_actions[name] == nil then new_actions[name] = {} end - new_actions[name][parser.r_type] = { ttl=parser.r_ttl, rdata=rdata } + local act = new_actions[name][parser.r_type] + if act == nil then + new_actions[name][parser.r_type] = { ttl=parser.r_ttl, rdata=rdata } + else -- mutiple RRs: no reordering or deduplication + if type(act.rdata) ~= 'table' then + act.rdata = { act.rdata } + end + table.insert(act.rdata, rdata) + if parser.r_ttl ~= act.ttl then -- be conservative + log('[poli] RPZ %s:%d warning: different TTLs in a set (minimum taken)', + path, tonumber(parser.line_counter)) + act.ttl = math.min(act.ttl, parser.r_ttl) + end + end + else + assert(is_bad == false and prefix_labels == 0) end end end @@ -598,7 +609,8 @@ req.add_selected.len = 0 -- Let's be defensive and clear the answer, too. - local pkt = req.answer + local pkt = req:ensure_answer() + if pkt == nil then return nil end pkt:clear_payload() return pkt end @@ -611,6 +623,7 @@ return function (_, req) -- Write authority information local answer = answer_clear(req) + if answer == nil then return nil end ffi.C.kr_pkt_make_auth_header(answer) answer:rcode(kres.rcode.NXDOMAIN) answer:begin(kres.section.AUTHORITY) @@ -641,9 +654,14 @@ ffi.C.kr_log_req(req, 0, 0, 'dbg', 'following rrsets were marked as interesting:\n' .. req:selected_tostring()) - ffi.C.kr_log_req(req, 0, 0, 'dbg', - 'answer packet:\n' .. - tostring(req.answer)) + if req.answer ~= nil then + ffi.C.kr_log_req(req, 0, 0, 'dbg', + 'answer packet:\n' .. + tostring(req.answer)) + else + ffi.C.kr_log_req(req, 0, 0, 'dbg', + 'answer packet DROPPED\n') + end end) ffi.gc(debug_logfinish_cb, free_cb) @@ -698,24 +716,27 @@ policy.DENY = policy.DENY_MSG() -- compatibility with < 2.0 function policy.DROP(_, req) - answer_clear(req) + local answer = answer_clear(req) + if answer == nil then return nil end return kres.FAIL end function policy.REFUSE(_, req) local answer = answer_clear(req) + if answer == nil then return nil end answer:rcode(kres.rcode.REFUSED) answer:ad(false) return kres.DONE end function policy.TC(state, req) - -- Skip non-UDP queries - if req.answer.max_size == 65535 then + -- Avoid non-UDP queries + if req.qsource.addr == nil or req.qsource.flags.tcp then return state end local answer = answer_clear(req) + if answer == nil then return nil end answer:tc(1) answer:ad(false) return kres.DONE diff -Nru knot-resolver-5.1.1/modules/policy/policy.rpz.test.lua knot-resolver-5.2.1/modules/policy/policy.rpz.test.lua --- knot-resolver-5.1.1/modules/policy/policy.rpz.test.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/policy/policy.rpz.test.lua 2020-12-09 09:44:29.000000000 +0000 @@ -14,41 +14,15 @@ c:commit() end -local function rrset_to_texts(rr) - local rr_text = {} - for w in rr:txt_dump():gmatch("%S+") do table.insert(rr_text, w) end - return rr_text -end - -local function check_answer(desc, qname, qtype, expected_rcode, expected_rdata) - qtype_str = kres.tostring.type[qtype] - callback = function(pkt) - same(pkt:rcode(), expected_rcode, - desc .. ': expecting answer for query ' .. qname .. ' ' .. qtype_str - .. ' with rcode ' .. kres.tostring.rcode[expected_rcode]) - - if expected_rdata then - rr_text = rrset_to_texts(pkt:rrsets(kres.section.ANSWER)[1]) - ok(rr_text[4] == expected_rdata, - desc ..': checking rdata of answer for ' .. qname .. ' ' .. qtype_str) - else - -- check empty section - ok(pkt:rrsets(kres.section.ANSWER)[1] == nil, - desc ..': checking empty answer section for ' .. qname .. ' ' .. qtype_str) - end - - end - - resolve(qname, qtype, kres.class.IN, {}, callback) -end +local check_answer = require('test_utils').check_answer local function test_rpz() check_answer('"CNAME ." return NXDOMAIN', 'nxdomain.', kres.type.A, kres.rcode.NXDOMAIN) check_answer('"CNAME *." return NODATA', - 'nodata.', kres.type.A, kres.rcode.NOERROR) + 'nodata.', kres.type.A, kres.rcode.NOERROR, {}) check_answer('"CNAME *. on wildcard" return NODATA', - 'nodata.nxdomain.', kres.type.A, kres.rcode.NOERROR) + 'nodata.nxdomain.', kres.type.A, kres.rcode.NOERROR, {}) check_answer('"CNAME rpz-drop." be dropped', 'rpzdrop.', kres.type.A, kres.rcode.SERVFAIL) check_answer('"CNAME rpz-passthru" return A rrset', @@ -60,11 +34,14 @@ check_answer('"A 192.168.7.7" with suffixed zone name in owner return local A rrset', 'testdomain.rra.', kres.type.A, kres.rcode.NOERROR, '192.168.7.7') check_answer('non existing AAAA on rra domain return NODATA', - 'rra.', kres.type.AAAA, kres.rcode.NOERROR) + 'rra.', kres.type.AAAA, kres.rcode.NOERROR, {}) check_answer('"A 192.168.8.8" and domain with uppercase and lowercase letters', 'case.sensitive.', kres.type.A, kres.rcode.NOERROR, '192.168.8.8') check_answer('"A 192.168.8.8" and domain with uppercase and lowercase letters', 'CASe.SENSItivE.', kres.type.A, kres.rcode.NOERROR, '192.168.8.8') + check_answer('two AAAA records', + 'two.records.', kres.type.AAAA, kres.rcode.NOERROR, + {'2001:db8::2', '2001:db8::1'}) end net.ipv4 = false diff -Nru knot-resolver-5.1.1/modules/policy/policy.slice.test.lua knot-resolver-5.2.1/modules/policy/policy.slice.test.lua --- knot-resolver-5.1.1/modules/policy/policy.slice.test.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/policy/policy.slice.test.lua 2020-12-09 09:44:29.000000000 +0000 @@ -39,7 +39,8 @@ slice_queries[index][name] = count + 1 -- refuse query - local answer = req.answer + local answer = req:ensure_answer() + if answer == nil then return nil end answer:rcode(kres.rcode.REFUSED) answer:ad(false) return kres.DONE diff -Nru knot-resolver-5.1.1/modules/policy/policy.test.rpz knot-resolver-5.2.1/modules/policy/policy.test.rpz --- knot-resolver-5.1.1/modules/policy/policy.test.rpz 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/policy/policy.test.rpz 2020-12-09 09:44:29.000000000 +0000 @@ -1,13 +1,18 @@ +$ORIGIN testdomain. $TTL 30 testdomain. SOA nonexistent.testdomain. testdomain. 1 12h 15m 3w 2h NS nonexistent.testdomain. -nxdomain. CNAME . -nodata. CNAME *. -*.nxdomain. CNAME *. -rpzdrop. CNAME rpz-drop. -rpzpassthru. CNAME rpz-passthru. -rra. A 192.168.5.5 -rra-zonename-suffix.testdomain. A 192.168.6.6 -testdomain.rra.testdomain. A 192.168.7.7 -CaSe.SeNSiTiVe. A 192.168.8.8 +nxdomain CNAME . +nodata CNAME *. +*.nxdomain CNAME *. +rpzdrop CNAME rpz-drop. +rpzpassthru CNAME rpz-passthru. +rra A 192.168.5.5 +rra-zonename-suffix A 192.168.6.6 +testdomain.rra.testdomain. A 192.168.7.7 +CaSe.SeNSiTiVe A 192.168.8.8 + +two.records AAAA 2001:db8::2 +two.records AAAA 2001:db8::1 + diff -Nru knot-resolver-5.1.1/modules/policy/README.rst knot-resolver-5.2.1/modules/policy/README.rst --- knot-resolver-5.1.1/modules/policy/README.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/policy/README.rst 2020-12-09 09:44:29.000000000 +0000 @@ -110,10 +110,10 @@ .. code-block:: lua - -- Whitelist 'www.badboy.cz' - policy.add(policy.pattern(policy.PASS, todname('www.badboy.cz.'))) - -- Block all names below badboy.cz - policy.add(policy.suffix(policy.DENY, {todname('badboy.cz.')})) + -- Whitelist 'good.example.com' + policy.add(policy.pattern(policy.PASS, todname('good.example.com.'))) + -- Block all names below example.com + policy.add(policy.suffix(policy.DENY, {todname('example.com.')})) .. py:attribute:: DENY @@ -141,19 +141,43 @@ .. code-block:: lua - -- this policy is enforced on answers - -- therefore we have to use 'postrule' - -- (the "true" at the end of policy.add) - policy.add(policy.REROUTE({'192.0.2.0/24', '127.0.0.0'}), true) + -- this policy is enforced on answers + -- therefore we have to use 'postrule' + -- (the "true" at the end of policy.add) + policy.add(policy.REROUTE({'192.0.2.0/24', '127.0.0.0'}), true) + +.. function:: ANSWER({ type = { rdata=data, [ttl=1] } }, [nodata=false]) + + Overwrite Resource Records in responses with specified values. + + * type + - RR type to be replaced, e.g. ``[kres.type.A]`` or `numeric value `_. + * rdata + - RR data in DNS wire format, i.e. binary form specific for given RR type. Set of multiple RRs can be specified as table ``{ rdata1, rdata2, ... }``. Use helper function :func:`kres.str2ip` to generate wire format for A and AAAA records. + * ttl + - TTL in seconds. Default: 1 second. + * nodata + - If type requested by client is not configured in this policy: -.. function:: ANSWER({ type = { ttl=ttl, rdata=data} }, nodata) + - ``true``: Return empty answer (`NODATA`). + - ``false``: Ignore this policy and continue processing other rules. - Overwrite rr data in response. ``rdata`` takes just IP address. If `nodata` is `true` policy return `NODATA` when requested type from client isn't specified (default: ``nodata=false``). + Default: ``false``. .. code-block:: lua - -- this policy changes IPv4 adress and TTL for `exmaple.com` - policy.add(policy.suffix(policy.ANSWER({ [kres.type.A] = { ttl=300, rdata='\192\0\2\7' } }), { todname('example.com') })) + -- policy to change IPv4 address and TTL for example.com + policy.add( + policy.suffix( + policy.ANSWER( + { [kres.type.A] = { rdata=kres.str2ip('192.0.2.7'), ttl=300 } } + ), { todname('example.com') })) + -- policy to generate two TXT records (specified in binary format) for example.net + policy.add( + policy.suffix( + policy.ANSWER( + { [kres.type.TXT] = { rdata={'\005first', '\006second'}, ttl=5 } } + ), { todname('example.net') })) More complex non-chain actions are described in their own chapters, namely: @@ -171,7 +195,7 @@ .. code-block:: lua - policy.add(policy.all(policy.MIRROR('127.0.0.2'))) + policy.add(policy.all(policy.MIRROR('127.0.0.2'))) .. function:: FLAGS(set, clear) @@ -183,9 +207,9 @@ .. code-block:: lua - -- log answers from all authoritative servers involved in resolving - -- requests for example.net. and its subdomains - policy.add(policy.suffix(policy.QTRACE, policy.todnames({'example.net'}))) + -- log answers from all authoritative servers involved in resolving + -- requests for example.net. and its subdomains + policy.add(policy.suffix(policy.QTRACE, policy.todnames({'example.net'}))) .. py:attribute:: REQTRACE @@ -201,9 +225,9 @@ .. code-block:: lua - policy.add(policy.suffix( - policy.DEBUG_CACHE_MISS, - policy.todnames({'example.com.'}))) + policy.add(policy.suffix( + policy.DEBUG_CACHE_MISS, + policy.todnames({'example.com.'}))) .. py:function:: DEBUG_IF(test_function) @@ -219,11 +243,11 @@ .. code-block:: lua - policy.add(policy.suffix( - policy.DEBUG_IF(function(req) - return (req.state ~= kres.DONE) - end), - policy.todnames({'dnssec-failed.org.'}))) + policy.add(policy.suffix( + policy.DEBUG_IF(function(req) + return (req.state ~= kres.DONE) + end), + policy.todnames({'dnssec-failed.org.'}))) Custom actions @@ -239,20 +263,21 @@ .. code-block:: lua - -- Custom action which generates fake A record - local ffi = require('ffi') - local function fake_A_record(state, req) - local answer = req.answer - local qry = req:current() - if qry.stype ~= kres.type.A then - return state - end - ffi.C.kr_pkt_make_auth_header(answer) - answer:rcode(kres.rcode.NOERROR) - answer:begin(kres.section.ANSWER) - answer:put(qry.sname, 900, answer:qclass(), kres.type.A, '\192\168\1\3') - return kres.DONE - end + -- Custom action which generates fake A record + local ffi = require('ffi') + local function fake_A_record(state, req) + local answer = req:ensure_answer() + if answer == nil then return nil end + local qry = req:current() + if qry.stype ~= kres.type.A then + return state + end + ffi.C.kr_pkt_make_auth_header(answer) + answer:rcode(kres.rcode.NOERROR) + answer:begin(kres.section.ANSWER) + answer:put(qry.sname, 900, answer:qclass(), kres.type.A, '\192\168\1\3') + return kres.DONE + end This custom action can be used as any other built-in action. For example this applies our *fake A record action* and executes it on all queries in subtree ``example.net``: @@ -281,13 +306,11 @@ .. code-block:: lua - -- Forward all queries to public resolvers https://www.nic.cz/odvr - policy.add(policy.all( - policy.FORWARD( - {'2001:148f:fffe::1', '2001:148f:ffff::1', - '185.43.135.1', '193.14.47.1'}))) - - + -- Forward all queries to public resolvers https://www.nic.cz/odvr + policy.add(policy.all( + policy.FORWARD( + {'2001:148f:fffe::1', '2001:148f:ffff::1', + '185.43.135.1', '193.14.47.1'}))) A variant which uses encrypted DNS-over-TLS transport is called :func:`policy.TLS_FORWARD`, please see section :ref:`tls-forwarding`. @@ -302,13 +325,12 @@ .. code-block:: lua - -- Answers for reverse queries about the 192.168.1.0/24 subnet - -- are to be obtained from IP address 192.0.2.1 port 5353 - -- This disables DNSSEC validation! - policy.add(policy.suffix( - policy.STUB('192.0.2.1@5353'), - {todname('1.168.192.in-addr.arpa')})) - + -- Answers for reverse queries about the 192.168.1.0/24 subnet + -- are to be obtained from IP address 192.0.2.1 port 5353 + -- This disables DNSSEC validation! + policy.add(policy.suffix( + policy.STUB('192.0.2.1@5353'), + {todname('1.168.192.in-addr.arpa')})) .. _tls-forwarding: @@ -342,8 +364,8 @@ .. code-block:: lua - policy.TLS_FORWARD({ - {'2001:DB8::d0c', hostname='res.example.com'}}) + policy.TLS_FORWARD({ + {'2001:DB8::d0c', hostname='res.example.com'}}) - ``hostname`` must be a valid domain name matching server's certificate. It will also be sent to the server as SNI_. - ``ca_file`` optionally contains a path to a CA certificate (or certificate bundle) in `PEM format`_. @@ -363,24 +385,24 @@ .. code-block:: lua - modules = { 'policy' } - -- forward all queries over TLS to the specified server - policy.add(policy.all(policy.TLS_FORWARD({{'192.0.2.1', pin_sha256='YQ=='}}))) - -- for brevity, other TLS examples omit policy.add(policy.all()) - -- single server authenticated using its certificate pin_sha256 - policy.TLS_FORWARD({{'192.0.2.1', pin_sha256='YQ=='}}) -- pin_sha256 is base64-encoded - -- single server authenticated using hostname and system-wide CA certificates - policy.TLS_FORWARD({{'192.0.2.1', hostname='res.example.com'}}) - -- single server using non-standard port - policy.TLS_FORWARD({{'192.0.2.1@443', pin_sha256='YQ=='}}) -- use @ or # to specify port - -- single server with multiple valid pins (e.g. anycast) - policy.TLS_FORWARD({{'192.0.2.1', pin_sha256={'YQ==', 'Wg=='}}) - -- multiple servers, each with own authenticator - policy.TLS_FORWARD({ -- please note that { here starts list of servers - {'192.0.2.1', pin_sha256='Wg=='}, - -- server must present certificate issued by specified CA and hostname must match - {'2001:DB8::d0c', hostname='res.example.com', ca_file='/etc/knot-resolver/tlsca.crt'} - }) + modules = { 'policy' } + -- forward all queries over TLS to the specified server + policy.add(policy.all(policy.TLS_FORWARD({{'192.0.2.1', pin_sha256='YQ=='}}))) + -- for brevity, other TLS examples omit policy.add(policy.all()) + -- single server authenticated using its certificate pin_sha256 + policy.TLS_FORWARD({{'192.0.2.1', pin_sha256='YQ=='}}) -- pin_sha256 is base64-encoded + -- single server authenticated using hostname and system-wide CA certificates + policy.TLS_FORWARD({{'192.0.2.1', hostname='res.example.com'}}) + -- single server using non-standard port + policy.TLS_FORWARD({{'192.0.2.1@443', pin_sha256='YQ=='}}) -- use @ or # to specify port + -- single server with multiple valid pins (e.g. anycast) + policy.TLS_FORWARD({{'192.0.2.1', pin_sha256={'YQ==', 'Wg=='}}) + -- multiple servers, each with own authenticator + policy.TLS_FORWARD({ -- please note that { here starts list of servers + {'192.0.2.1', pin_sha256='Wg=='}, + -- server must present certificate issued by specified CA and hostname must match + {'2001:DB8::d0c', hostname='res.example.com', ca_file='/etc/knot-resolver/tlsca.crt'} + }) Forwarding to multiple targets ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -502,17 +524,17 @@ The easiest work-around is to disable reading from cache for grafted domains. .. code-block:: lua - :caption: Example configuration grafting domains onto public DNS namespace + :caption: Example configuration grafting domains onto public DNS namespace - extraTrees = policy.todnames( - {'faketldtest.', - 'sld.example.', - 'internal.example.com.', - '2.0.192.in-addr.arpa.' -- this applies to reverse DNS tree as well - }) - -- Beware: the rule order is important, as STUB is not a chain action. - policy.add(policy.suffix(policy.FLAGS({'NO_CACHE'}), extraTrees)) - policy.add(policy.suffix(policy.STUB({'2001:db8::1'}), extraTrees)) + extraTrees = policy.todnames( + {'faketldtest.', + 'sld.example.', + 'internal.example.com.', + '2.0.192.in-addr.arpa.' -- this applies to reverse DNS tree as well + }) + -- Beware: the rule order is important, as STUB is not a chain action. + policy.add(policy.suffix(policy.FLAGS({'NO_CACHE'}), extraTrees)) + policy.add(policy.suffix(policy.STUB({'2001:db8::1'}), extraTrees)) Response policy zones --------------------- @@ -572,7 +594,7 @@ .. code-block:: lua - policy.add( + policy.add( policy.rpz(policy.DENY_MSG('domain blocked by your resolver operator'), '/etc/knot-resolver/blocklist.rpz', true)) @@ -600,10 +622,10 @@ .. code-block:: lua - -- mirror all queriesm, keep handle so we can retrieve information later - local rule = policy.add(policy.all(policy.MIRROR('127.0.0.2'))) - -- we can print statistics about this rule any time later - print(string.format('id: %d, matched queries: %d', rule.id, rule.count) + -- mirror all queriesm, keep handle so we can retrieve information later + local rule = policy.add(policy.all(policy.MIRROR('127.0.0.2'))) + -- we can print statistics about this rule any time later + print(string.format('id: %d, matched queries: %d', rule.id, rule.count) .. function:: del(id) diff -Nru knot-resolver-5.1.1/modules/policy/test.integr/deckard.yaml knot-resolver-5.2.1/modules/policy/test.integr/deckard.yaml --- knot-resolver-5.1.1/modules/policy/test.integr/deckard.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/policy/test.integr/deckard.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -3,8 +3,7 @@ - name: kresd binary: kresd additional: - - -f - - "1" + - --noninteractive templates: - modules/policy/test.integr/kresd_config.j2 - tests/integration/hints_zone.j2 diff -Nru knot-resolver-5.1.1/modules/predict/packaging/test.config knot-resolver-5.2.1/modules/predict/packaging/test.config --- knot-resolver-5.1.1/modules/predict/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/predict/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('predict') -assert(predict) -quit() diff -Nru knot-resolver-5.1.1/modules/predict/.packaging/test.config knot-resolver-5.2.1/modules/predict/.packaging/test.config --- knot-resolver-5.1.1/modules/predict/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/predict/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('predict') +assert(predict) +quit() diff -Nru knot-resolver-5.1.1/modules/prefill/packaging/rundeps knot-resolver-5.2.1/modules/prefill/packaging/rundeps --- knot-resolver-5.1.1/modules/prefill/packaging/rundeps 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/prefill/packaging/rundeps 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -lua-http diff -Nru knot-resolver-5.1.1/modules/prefill/packaging/test.config knot-resolver-5.2.1/modules/prefill/packaging/test.config --- knot-resolver-5.1.1/modules/prefill/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/prefill/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('prefill') -assert(prefill) -quit() diff -Nru knot-resolver-5.1.1/modules/prefill/.packaging/test.config knot-resolver-5.2.1/modules/prefill/.packaging/test.config --- knot-resolver-5.1.1/modules/prefill/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/prefill/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('prefill') +assert(prefill) +quit() diff -Nru knot-resolver-5.1.1/modules/prefill/prefill.test/prefill.test.lua knot-resolver-5.2.1/modules/prefill/prefill.test/prefill.test.lua --- knot-resolver-5.1.1/modules/prefill/prefill.test/prefill.test.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/prefill/prefill.test/prefill.test.lua 2020-12-09 09:44:29.000000000 +0000 @@ -36,25 +36,13 @@ trust_anchors.remove('.') trust_anchors.add('. IN DS 18213 7 2 A1D391053583A4BC597DB9588B296060FC55EAC80B3831CA371BA1FA AE997244') -local function check_answer(desc, qname, qtype, expected_rcode) - qtype_str = kres.tostring.type[qtype] - callback = function(pkt) - same(pkt:rcode(), expected_rcode, - desc .. ': expecting answer for query ' .. qname .. ' ' .. qtype_str - .. ' with rcode ' .. kres.tostring.rcode[expected_rcode] .. ' got ' .. kres.tostring.rcode[pkt:rcode()]) - - ok((pkt:ancount() > 0) == (pkt:rcode() == kres.rcode.NOERROR), - desc ..': checking number of answers for ' .. qname .. ' ' .. qtype_str) - end - resolve(qname, qtype, kres.class.IN, {}, callback) -end - -- do not attempt to contact outside world, operate only on cache net.ipv4 = false net.ipv6 = false -- do not listen, test is driven by config code env.KRESD_NO_LISTEN = true +local check_answer = require('test_utils').check_answer local function import_valid_root_zone() cache.clear() diff -Nru knot-resolver-5.1.1/modules/priming/packaging/test.config knot-resolver-5.2.1/modules/priming/packaging/test.config --- knot-resolver-5.1.1/modules/priming/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/priming/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('priming') -assert(priming) -quit() diff -Nru knot-resolver-5.1.1/modules/priming/.packaging/test.config knot-resolver-5.2.1/modules/priming/.packaging/test.config --- knot-resolver-5.1.1/modules/priming/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/priming/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('priming') +assert(priming) +quit() diff -Nru knot-resolver-5.1.1/modules/priming/priming.lua knot-resolver-5.2.1/modules/priming/priming.lua --- knot-resolver-5.1.1/modules/priming/priming.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/priming/priming.lua 2020-12-09 09:44:29.000000000 +0000 @@ -42,7 +42,8 @@ -- When all response is processed internal.nsset is published in resolver engine -- luacheck: no unused args local function address_callback(pkt, req) - if pkt:rcode() ~= kres.rcode.NOERROR then + if pkt == nil or pkt:rcode() ~= kres.rcode.NOERROR then + pkt = req.qsource.packet warn("[priming] cannot resolve address '%s', type: %d", kres.dname2str(pkt:qname()), pkt:qtype()) else local section = pkt:rrsets(kres.section.ANSWER) @@ -80,7 +81,7 @@ -- These new queries should be resolved from cache. -- luacheck: no unused args local function priming_callback(pkt, req) - if pkt:rcode() ~= kres.rcode.NOERROR then + if pkt == nil or pkt:rcode() ~= kres.rcode.NOERROR then warn("[priming] cannot resolve '.' NS, next priming query in %d seconds", priming.retry_time / sec) internal.event = event.after(priming.retry_time, internal.prime) return nil diff -Nru knot-resolver-5.1.1/modules/rebinding/packaging/test.config knot-resolver-5.2.1/modules/rebinding/packaging/test.config --- knot-resolver-5.1.1/modules/rebinding/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/rebinding/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('rebinding') -assert(rebinding) -quit() diff -Nru knot-resolver-5.1.1/modules/rebinding/.packaging/test.config knot-resolver-5.2.1/modules/rebinding/.packaging/test.config --- knot-resolver-5.1.1/modules/rebinding/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/rebinding/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('rebinding') +assert(rebinding) +quit() diff -Nru knot-resolver-5.1.1/modules/rebinding/rebinding.lua knot-resolver-5.2.1/modules/rebinding/rebinding.lua --- knot-resolver-5.1.1/modules/rebinding/rebinding.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/rebinding/rebinding.lua 2020-12-09 09:44:29.000000000 +0000 @@ -71,13 +71,15 @@ local function refuse(req) policy.REFUSE(nil, req) - local pkt = req.answer + local pkt = req:ensure_answer() + if pkt == nil then return nil end pkt:aa(false) pkt:begin(kres.section.ADDITIONAL) local msg = 'blocked by DNS rebinding protection' pkt:put('\11explanation\7invalid\0', 10800, pkt:qclass(), kres.type.TXT, string.char(#msg) .. msg) + return kres.DONE end -- act on DNS queries which were not answered from cache @@ -102,14 +104,16 @@ Typical example: NS address resolution -> only this NS won't be used but others may still be OK (or we SERVFAIL due to no NS being usable). --]] - if qry.parent == nil then refuse(req) end + if qry.parent == nil then + state = refuse(req) + end if verbose() then ffi.C.kr_log_q(qry, 'rebinding', 'blocking blacklisted IP in RR \'%s\' received from IP %s\n', kres.rr2str(bad_rr), tostring(kres.sockaddr_t(req.upstream.addr))) end - return kres.DONE + return state end return M diff -Nru knot-resolver-5.1.1/modules/rebinding/test.integr/deckard.yaml knot-resolver-5.2.1/modules/rebinding/test.integr/deckard.yaml --- knot-resolver-5.1.1/modules/rebinding/test.integr/deckard.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/rebinding/test.integr/deckard.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -3,8 +3,7 @@ - name: kresd binary: kresd additional: - - -f - - "1" + - --noninteractive templates: - modules/rebinding/test.integr/kresd_config.j2 - tests/integration/hints_zone.j2 diff -Nru knot-resolver-5.1.1/modules/refuse_nord/meson.build knot-resolver-5.2.1/modules/refuse_nord/meson.build --- knot-resolver-5.1.1/modules/refuse_nord/meson.build 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/refuse_nord/meson.build 2020-12-09 09:44:29.000000000 +0000 @@ -2,7 +2,7 @@ # C module: refuse_nord integr_tests += [ - ['refuse_nord', join_paths(meson.current_source_dir(), 'test.integr')], + ['refuse_nord', meson.current_source_dir() / 'test.integr'], ] refuse_nord_src = files([ diff -Nru knot-resolver-5.1.1/modules/refuse_nord/.packaging/test.config knot-resolver-5.2.1/modules/refuse_nord/.packaging/test.config --- knot-resolver-5.1.1/modules/refuse_nord/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/refuse_nord/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,3 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +assert(modules.load('refuse_nord') == true) +quit() diff -Nru knot-resolver-5.1.1/modules/refuse_nord/refuse_nord.c knot-resolver-5.2.1/modules/refuse_nord/refuse_nord.c --- knot-resolver-5.1.1/modules/refuse_nord/refuse_nord.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/refuse_nord/refuse_nord.c 2020-12-09 09:44:29.000000000 +0000 @@ -13,14 +13,15 @@ { struct kr_request *req = ctx->req; uint8_t rd = knot_wire_get_rd(req->qsource.packet->wire); + if (rd) + return ctx->state; - if (!rd) { - knot_pkt_t *answer = req->answer; - knot_wire_set_rcode(answer->wire, KNOT_RCODE_REFUSED); - knot_wire_clear_ad(answer->wire); - ctx->state = KR_STATE_DONE; - } - + knot_pkt_t *answer = kr_request_ensure_answer(req); + if (!answer) + return ctx->state; + knot_wire_set_rcode(answer->wire, KNOT_RCODE_REFUSED); + knot_wire_clear_ad(answer->wire); + ctx->state = KR_STATE_DONE; return ctx->state; } diff -Nru knot-resolver-5.1.1/modules/refuse_nord/test.integr/deckard.yaml knot-resolver-5.2.1/modules/refuse_nord/test.integr/deckard.yaml --- knot-resolver-5.1.1/modules/refuse_nord/test.integr/deckard.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/refuse_nord/test.integr/deckard.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -3,8 +3,7 @@ - name: kresd binary: kresd additional: - - -f - - "1" + - --noninteractive templates: - modules/refuse_nord/test.integr/kresd_config.j2 - tests/integration/hints_zone.j2 diff -Nru knot-resolver-5.1.1/modules/renumber/packaging/test.config knot-resolver-5.2.1/modules/renumber/packaging/test.config --- knot-resolver-5.1.1/modules/renumber/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/renumber/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('renumber') -assert(renumber) -quit() diff -Nru knot-resolver-5.1.1/modules/renumber/.packaging/test.config knot-resolver-5.2.1/modules/renumber/.packaging/test.config --- knot-resolver-5.1.1/modules/renumber/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/renumber/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('renumber') +assert(renumber) +quit() diff -Nru knot-resolver-5.1.1/modules/renumber/renumber.lua knot-resolver-5.2.1/modules/renumber/renumber.lua --- knot-resolver-5.1.1/modules/renumber/renumber.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/renumber/renumber.lua 2020-12-09 09:44:29.000000000 +0000 @@ -115,7 +115,7 @@ if type(conf) ~= 'table' or type(conf[1]) ~= 'table' then error('[renumber] expected { {prefix, target}, ... }') end - for i = 1, #conf do add_prefix(conf[i][1], conf[1][2]) end + for i = 1, #conf do add_prefix(conf[i][1], conf[i][2]) end end -- Layers diff -Nru knot-resolver-5.1.1/modules/renumber/renumber.test.lua knot-resolver-5.2.1/modules/renumber/renumber.test.lua --- knot-resolver-5.1.1/modules/renumber/renumber.test.lua 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/renumber/renumber.test.lua 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,87 @@ +local function gen_rrset(owner, rrtype, rdataset) + assert(type(rdataset) == 'table' or type(rdataset) == 'string') + if type(rdataset) ~= 'table' then + rdataset = { rdataset } + end + local rrset = kres.rrset(todname(owner), rrtype, kres.class.IN, 3600) + for _, rdata in pairs(rdataset) do + assert(rrset:add_rdata(rdata, #rdata)) + end + return rrset +end + +local function prepare_cache() + cache.open(100*MB) + cache.clear() + + local ffi = require('ffi') + local c = kres.context().cache + + assert(c:insert( + gen_rrset('a10-0.test.', + kres.type.A, kres.str2ip('10.0.0.1')), + nil, ffi.C.KR_RANK_SECURE + ffi.C.KR_RANK_AUTH)) + assert(c:insert( + gen_rrset('a10-2.test.', + kres.type.A, kres.str2ip('10.2.0.1')), + nil, ffi.C.KR_RANK_SECURE + ffi.C.KR_RANK_AUTH)) + assert(c:insert( + gen_rrset('a10-0plus2.test.', + kres.type.A, { + kres.str2ip('10.0.0.1'), + kres.str2ip('10.2.0.1') + }), + nil, ffi.C.KR_RANK_SECURE + ffi.C.KR_RANK_AUTH)) + assert(c:insert( + gen_rrset('a166-66.test.', + kres.type.A, kres.str2ip('166.66.42.123')), + nil, ffi.C.KR_RANK_SECURE + ffi.C.KR_RANK_AUTH)) + assert(c:insert( + gen_rrset('aaaa-db8-1.test.', + kres.type.AAAA, { + kres.str2ip('2001:db8:1::1'), + kres.str2ip('2001:db8:1::2'), + }), + nil, ffi.C.KR_RANK_SECURE + ffi.C.KR_RANK_AUTH)) + + c:commit() +end + +local check_answer = require('test_utils').check_answer + +local function test_renumber() + check_answer('unknown IPv4 range passes through unaffected', + 'a10-0.test.', kres.type.A, kres.rcode.NOERROR, '10.0.0.1') + check_answer('known IPv4 range is remapped when matching first-defined rule', + 'a10-2.test.', kres.type.A, kres.rcode.NOERROR, '192.168.2.1') + check_answer('mix of known and unknown IPv4 ranges is remapped correctly', + 'a10-0plus2.test.', kres.type.A, kres.rcode.NOERROR, {'192.168.2.1', '10.0.0.1'}) + check_answer('known IPv4 range is remapped when matching second-defined rule', + 'a166-66.test.', kres.type.A, kres.rcode.NOERROR, '127.0.42.123') + + + check_answer('two AAAA records', + 'aaaa-db8-1.test.', kres.type.AAAA, kres.rcode.NOERROR, + {'2001:db8:2::2', '2001:db8:2::1'}) +end + +net.ipv4 = false +net.ipv6 = false + +trust_anchors.remove('.') +policy.add(policy.all(policy.DEBUG_ALWAYS)) +policy.add(policy.suffix(policy.PASS, {todname('test.')})) +prepare_cache() + +verbose(true) +modules.load('renumber < cache') +renumber.config({ + -- Source subnet, destination subnet + {'10.2.0.0/24', '192.168.2.0'}, + {'166.66.0.0/16', '127.0.0.0'}, + {'2001:db8:1::/48', '2001:db8:2::'}, +}) + +return { + test_renumber, +} diff -Nru knot-resolver-5.1.1/modules/serve_stale/packaging/test.config knot-resolver-5.2.1/modules/serve_stale/packaging/test.config --- knot-resolver-5.1.1/modules/serve_stale/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/serve_stale/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('serve_stale') -assert(serve_stale) -quit() diff -Nru knot-resolver-5.1.1/modules/serve_stale/.packaging/test.config knot-resolver-5.2.1/modules/serve_stale/.packaging/test.config --- knot-resolver-5.1.1/modules/serve_stale/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/serve_stale/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('serve_stale') +assert(serve_stale) +quit() diff -Nru knot-resolver-5.1.1/modules/serve_stale/test.integr/deckard.yaml knot-resolver-5.2.1/modules/serve_stale/test.integr/deckard.yaml --- knot-resolver-5.1.1/modules/serve_stale/test.integr/deckard.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/serve_stale/test.integr/deckard.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -3,8 +3,7 @@ - name: kresd binary: kresd additional: - - -f - - "1" + - --noninteractive templates: - modules/serve_stale/test.integr/kresd_config.j2 - tests/integration/hints_zone.j2 diff -Nru knot-resolver-5.1.1/modules/stats/meson.build knot-resolver-5.2.1/modules/stats/meson.build --- knot-resolver-5.1.1/modules/stats/meson.build 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/stats/meson.build 2020-12-09 09:44:29.000000000 +0000 @@ -7,7 +7,7 @@ c_src_lint += stats_src integr_tests += [ - ['stats', join_paths(meson.current_source_dir(), 'test.integr')], + ['stats', meson.current_source_dir() / 'test.integr'], ] diff -Nru knot-resolver-5.1.1/modules/stats/packaging/test.config knot-resolver-5.2.1/modules/stats/packaging/test.config --- knot-resolver-5.1.1/modules/stats/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/stats/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('stats') -assert(stats) -quit() diff -Nru knot-resolver-5.1.1/modules/stats/.packaging/test.config knot-resolver-5.2.1/modules/stats/.packaging/test.config --- knot-resolver-5.1.1/modules/stats/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/stats/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('stats') +assert(stats) +quit() diff -Nru knot-resolver-5.1.1/modules/stats/README.rst knot-resolver-5.2.1/modules/stats/README.rst --- knot-resolver-5.1.1/modules/stats/README.rst 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/stats/README.rst 2020-12-09 09:44:29.000000000 +0000 @@ -44,6 +44,9 @@ | request.doh | external requests received over | | | DNS-over-HTTP (:rfc:`8484`) | +------------------+----------------------------------------------+ +| request.xdp | external requests received over plain UDP | +| | via an AF_XDP socket | ++------------------+----------------------------------------------+ +----------------------------------------------------+ | **Global answer counters** | diff -Nru knot-resolver-5.1.1/modules/stats/stats.c knot-resolver-5.2.1/modules/stats/stats.c --- knot-resolver-5.1.1/modules/stats/stats.c 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/stats/stats.c 2020-12-09 09:44:29.000000000 +0000 @@ -44,7 +44,7 @@ X(answer,aa) X(answer,tc) X(answer,rd) X(answer,ra) X(answer, ad) X(answer,cd) \ X(answer,edns0) X(answer,do) \ X(query,edns) X(query,dnssec) \ - X(request,total) X(request,udp) X(request,tcp) \ + X(request,total) X(request,udp) X(request,tcp) X(request,xdp) \ X(request,dot) X(request,doh) X(request,internal) \ X(const,end) @@ -118,7 +118,7 @@ return key_len + sizeof(type); } -static void collect_sample(struct stat_data *data, struct kr_rplan *rplan, knot_pkt_t *pkt) +static void collect_sample(struct stat_data *data, struct kr_rplan *rplan) { /* Sample key = {[2] type, [1-255] owner} */ char key[sizeof(uint16_t) + KNOT_DNAME_MAXLEN]; @@ -186,7 +186,7 @@ /** * Count each transport only once, - * i.e. DoT does not count as TCP. + * i.e. DoT does not count as TCP and XDP does not count as UDP. */ if (req->qsource.flags.http) stat_const_add(data, metric_request_doh, 1); @@ -194,6 +194,8 @@ stat_const_add(data, metric_request_dot, 1); else if (req->qsource.flags.tcp) stat_const_add(data, metric_request_tcp, 1); + else if (req->qsource.flags.xdp) + stat_const_add(data, metric_request_xdp, 1); else stat_const_add(data, metric_request_udp, 1); return ctx->state; @@ -206,9 +208,14 @@ struct kr_rplan *rplan = ¶m->rplan; struct stat_data *data = module->data; + collect_sample(data, rplan); + if (!param->answer) { + /* The answer is being dropped. TODO: perhaps add some stat for this? */ + return ctx->state; + } + /* Collect data on final answer */ collect_answer(data, param->answer); - collect_sample(data, rplan, param->answer); /* Count cached and unresolved */ if (rplan->resolved.len > 0) { /* Histogram of answer latency. */ @@ -479,9 +486,9 @@ if (array_reserve(data->upstreams.q, UPSTREAMS_COUNT) != 0) { return kr_error(ENOMEM); } + data->upstreams.q.len = UPSTREAMS_COUNT; /* signify we use the entries */ for (size_t i = 0; i < UPSTREAMS_COUNT; ++i) { - struct sockaddr *sa = (struct sockaddr *)&data->upstreams.q.at[i]; - sa->sa_family = AF_UNSPEC; + data->upstreams.q.at[i].sin6_family = AF_UNSPEC; } return kr_ok(); } diff -Nru knot-resolver-5.1.1/modules/stats/test.integr/deckard.yaml knot-resolver-5.2.1/modules/stats/test.integr/deckard.yaml --- knot-resolver-5.1.1/modules/stats/test.integr/deckard.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/stats/test.integr/deckard.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -3,8 +3,7 @@ - name: kresd binary: kresd additional: - - -f - - "1" + - --noninteractive templates: - modules/stats/test.integr/kresd_config.j2 - tests/integration/hints_zone.j2 diff -Nru knot-resolver-5.1.1/modules/ta_sentinel/packaging/test.config knot-resolver-5.2.1/modules/ta_sentinel/packaging/test.config --- knot-resolver-5.1.1/modules/ta_sentinel/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/ta_sentinel/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('ta_sentinel') -assert(ta_sentinel) -quit() diff -Nru knot-resolver-5.1.1/modules/ta_sentinel/.packaging/test.config knot-resolver-5.2.1/modules/ta_sentinel/.packaging/test.config --- knot-resolver-5.1.1/modules/ta_sentinel/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/ta_sentinel/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('ta_sentinel') +assert(ta_sentinel) +quit() diff -Nru knot-resolver-5.1.1/modules/ta_sentinel/ta_sentinel.lua knot-resolver-5.2.1/modules/ta_sentinel/ta_sentinel.lua --- knot-resolver-5.1.1/modules/ta_sentinel/ta_sentinel.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/ta_sentinel/ta_sentinel.lua 2020-12-09 09:44:29.000000000 +0000 @@ -4,6 +4,7 @@ local ffi = require('ffi') function M.layer.finish(state, req, pkt) + if pkt == nil then return end -- fast filter by the length of the first QNAME label if pkt.wire[5] == 0 then return state end -- QDCOUNT % 256 == 0, in case we produced that local label_len = pkt.wire[12] diff -Nru knot-resolver-5.1.1/modules/ta_signal_query/packaging/test.config knot-resolver-5.2.1/modules/ta_signal_query/packaging/test.config --- knot-resolver-5.1.1/modules/ta_signal_query/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/ta_signal_query/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('ta_signal_query') -assert(ta_signal_query) -quit() diff -Nru knot-resolver-5.1.1/modules/ta_signal_query/.packaging/test.config knot-resolver-5.2.1/modules/ta_signal_query/.packaging/test.config --- knot-resolver-5.1.1/modules/ta_signal_query/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/ta_signal_query/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('ta_signal_query') +assert(ta_signal_query) +quit() diff -Nru knot-resolver-5.1.1/modules/ta_update/meson.build knot-resolver-5.2.1/modules/ta_update/meson.build --- knot-resolver-5.1.1/modules/ta_update/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/ta_update/meson.build 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,19 @@ +# LUA module: ta_update +# SPDX-License-Identifier: GPL-3.0-or-later + +config_tests += [ + ['ta_update', files('ta_update.test.lua'), ['snowflake']], +] + +integr_tests += [ + ['ta_update', meson.current_source_dir() / 'ta_update.test.integr'], + ['ta_update.unmanagedkey', meson.current_source_dir() / 'ta_update.unmanagedkey.test.integr'], +] + +lua_mod_src += [ + files('ta_update.lua'), +] + +install_data( + install_dir: modules_dir / 'ta_update', +) diff -Nru knot-resolver-5.1.1/modules/ta_update/packaging/test.config knot-resolver-5.2.1/modules/ta_update/packaging/test.config --- knot-resolver-5.1.1/modules/ta_update/packaging/test.config 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/ta_update/packaging/test.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ --- SPDX-License-Identifier: GPL-3.0-or-later -modules.load('ta_update') -assert(ta_update) -quit() diff -Nru knot-resolver-5.1.1/modules/ta_update/.packaging/test.config knot-resolver-5.2.1/modules/ta_update/.packaging/test.config --- knot-resolver-5.1.1/modules/ta_update/.packaging/test.config 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/ta_update/.packaging/test.config 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('ta_update') +assert(ta_update) +quit() diff -Nru knot-resolver-5.1.1/modules/ta_update/ta_update.lua knot-resolver-5.2.1/modules/ta_update/ta_update.lua --- knot-resolver-5.1.1/modules/ta_update/ta_update.lua 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/ta_update/ta_update.lua 2020-12-09 09:44:29.000000000 +0000 @@ -58,51 +58,54 @@ -- Evaluate TA status of a RR according to RFC5011. The time is in seconds. local function ta_present(keyset, rr, hold_down_time) -if rr.type == kres.type.DNSKEY and not C.kr_dnssec_key_ksk(rr.rdata) then - return false -- Ignore -end --- Attempt to extract key_tag -local key_tag = C.kr_dnssec_key_tag(rr.type, rr.rdata, #rr.rdata) -if key_tag < 0 or key_tag > 65535 then - warn(string.format('[ta_update] ignoring invalid or unsupported RR: %s: %s', - kres.rr2str(rr), ffi.string(C.knot_strerror(key_tag)))) - return false -end --- Find the key in current key set and check its status -local now = os.time() -local key_revoked = (rr.type == kres.type.DNSKEY) and C.kr_dnssec_key_revoked(rr.rdata) -local ta = ta_find(keyset, rr) -if ta then - -- Key reappears (KeyPres) - if ta.state == key_state.Missing then - ta.state = key_state.Valid - ta.timer = nil - end - -- Key is revoked (RevBit) - if ta.state == key_state.Valid or ta.state == key_state.Missing then - if key_revoked then - ta.state = key_state.Revoked - ta.timer = now + hold_down_time - end - end - -- Remove hold-down timer expires (RemTime) - if ta.state == key_state.Revoked and os.difftime(ta.timer, now) <= 0 then - ta.state = key_state.Removed - ta.timer = nil + if rr.type == kres.type.DNSKEY and not C.kr_dnssec_key_ksk(rr.rdata) then + return false -- Ignore end - -- Add hold-down timer expires (AddTime) - if ta.state == key_state.AddPend and os.difftime(ta.timer, now) <= 0 then - ta.state = key_state.Valid - ta.timer = nil + -- Attempt to extract key_tag + local key_tag = C.kr_dnssec_key_tag(rr.type, rr.rdata, #rr.rdata) + if key_tag < 0 or key_tag > 65535 then + warn(string.format('[ta_update] ignoring invalid or unsupported RR: %s: %s', + kres.rr2str(rr), ffi.string(C.knot_strerror(key_tag)))) + return false end - if rr.state ~= key_state.Valid or verbose() then - log('[ta_update] key: ' .. key_tag .. ' state: '..ta.state) + -- Find the key in current key set and check its status + local now = os.time() + local key_revoked = (rr.type == kres.type.DNSKEY) and C.kr_dnssec_key_revoked(rr.rdata) + local ta = ta_find(keyset, rr) + if ta then + -- Key reappears (KeyPres) + if ta.state == key_state.Missing then + ta.state = key_state.Valid + ta.timer = nil + end + -- Key is revoked (RevBit) + if ta.state == key_state.Valid or ta.state == key_state.Missing then + if key_revoked then + ta.state = key_state.Revoked + ta.timer = now + hold_down_time + end + end + -- Remove hold-down timer expires (RemTime) + if ta.state == key_state.Revoked and os.difftime(ta.timer, now) <= 0 then + ta.state = key_state.Removed + ta.timer = nil + end + -- Add hold-down timer expires (AddTime) + if ta.state == key_state.AddPend and os.difftime(ta.timer, now) <= 0 then + ta.state = key_state.Valid + ta.timer = nil + end + if rr.state ~= key_state.Valid or verbose() then + log('[ta_update] key: ' .. key_tag .. ' state: '..ta.state) + end + return true + elseif not key_revoked then -- First time seen (NewKey) + rr.state = key_state.AddPend + rr.key_tag = key_tag + rr.timer = now + hold_down_time + table.insert(keyset, rr) + return false end - return true -elseif not key_revoked then -- First time seen (NewKey) - rr.key_tag = key_tag - return false -end end -- TA is missing in the new key set. The time is in seconds. @@ -197,10 +200,59 @@ return true end +local function unmanagedkey_change(file_name) + warn('[ta_update] you need to update package with trust anchors in "%s" before it breaks', file_name) +end + +local function check_upstream(keyset, new_keys) + local process_keys = {} + + for _, rr in ipairs(new_keys) do + local key_revoked = (rr.type == kres.type.DNSKEY) and C.kr_dnssec_key_revoked(rr.rdata) + local ta = ta_find(keyset, rr) + table.insert(process_keys, ta) + + if rr.type == kres.type.DNSKEY and not C.kr_dnssec_key_ksk(rr.rdata) then + goto continue -- Ignore + end + + if not ta and not key_revoked then + -- I see new key + ta_update.cb_unmanagedkey_change(keyset.filename) + end + + if ta and key_revoked then + -- I see revoked key + ta_update.cb_unmanagedkey_change(keyset.filename) + end + + ::continue:: + end + + for _, rr in ipairs(keyset) do + local missing_rr = true + for _, rr_old in ipairs(process_keys) do + if (rr.owner == rr_old.owner) and (rr.type == rr_old.type) and (rr.type == kres.type.DNSKEY) then + if C.kr_dnssec_key_match(rr.rdata, #rr.rdata, rr_old.rdata, #rr_old.rdata) == 0 then + missing_rr = false + break + end + end + end + + if missing_rr then + -- This key is missing in the new keyset + ta_update.cb_unmanagedkey_change(keyset.filename) + end + end + +end + -- Refresh the DNSKEYs from the packet, and return time to the next check. -local function active_refresh(keyset, pkt) +local function active_refresh(keyset, pkt, req, managed) local retry = true - if pkt:rcode() == kres.rcode.NOERROR then + + if pkt ~= nil and pkt:rcode() == kres.rcode.NOERROR then local records = pkt:section(kres.section.ANSWER) local new_keys = {} for _, rr in ipairs(records) do @@ -208,11 +260,23 @@ table.insert(new_keys, rr) end end - update(keyset, new_keys) + + if managed then + update(keyset, new_keys) + else + check_upstream(keyset, new_keys) + end retry = false else - warn('[ta_update] active refresh failed for ' .. kres.dname2str(keyset.owner) - .. ' with rcode: ' .. pkt:rcode()) + local qry = req:initial() + if qry.flags.DNSSEC_BOGUS == true then + warn('[ta_update] active refresh failed, update your trust anchors in "%s"', keyset.filename) + elseif pkt == nil then + warn('[ta_update] active refresh failed, answer was dropped') + else + warn('[ta_update] active refresh failed for ' .. kres.dname2str(keyset.owner) + .. ' with rcode: ' .. pkt:rcode()) + end end -- Calculate refresh/retry timer (RFC 5011, 2.3) local min_ttl = retry and day or 15 * day @@ -223,7 +287,7 @@ end -- Plan an event for refreshing DNSKEYs and re-scheduling itself -local function refresh_plan(keyset, delay) +local function refresh_plan(keyset, delay, managed) local owner = keyset.owner local owner_str = kres.dname2str(keyset.owner) if not tracked_tas[owner] then @@ -236,13 +300,13 @@ track_cfg.event = event.after(delay, function () log('[ta_update] refreshing TA for ' .. owner_str) resolve(owner_str, kres.type.DNSKEY, kres.class.IN, 'NO_CACHE', - function (pkt) + function (pkt, req) -- Schedule itself with updated timeout - local delay_new = active_refresh(keyset, pkt) + local delay_new = active_refresh(keyset, pkt, req, managed) delay_new = keyset.refresh_time or ta_update.refresh_time or delay_new log('[ta_update] next refresh for ' .. owner_str .. ' in ' .. delay_new/hour .. ' hours') - refresh_plan(keyset, delay_new) + refresh_plan(keyset, delay_new, managed) end) end) end @@ -254,20 +318,17 @@ refresh_time = nil, keep_removed = 0, tracked = tracked_tas, -- debug and visibility, should not be changed by hand + cb_unmanagedkey_change = unmanagedkey_change, } -- start tracking (already loaded) TA with given zone name in wire format -- do first refresh immediatelly -function ta_update.start(zname) +function ta_update.start(zname, managed) local keyset = trust_anchors.keysets[zname] if not keyset then panic('[ta_update] TA must be configured first before tracking it') end - if not keyset.managed then - panic('[ta_update] TA is configured as unmanaged; remove it and ' - .. 'add it again as managed using trust_anchors.add_file()') - end - refresh_plan(keyset, 0) + refresh_plan(keyset, 0, managed) end function ta_update.stop(zname) diff -Nru knot-resolver-5.1.1/modules/ta_update/ta_update.test.integr/deckard.yaml knot-resolver-5.2.1/modules/ta_update/ta_update.test.integr/deckard.yaml --- knot-resolver-5.1.1/modules/ta_update/ta_update.test.integr/deckard.yaml 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/ta_update/ta_update.test.integr/deckard.yaml 2020-12-09 09:44:29.000000000 +0000 @@ -3,8 +3,7 @@ - name: kresd binary: kresd additional: - - -f - - "1" + - --noninteractive templates: - modules/ta_update/ta_update.test.integr/kresd_config.j2 - tests/integration/hints_zone.j2 diff -Nru knot-resolver-5.1.1/modules/ta_update/ta_update.test.integr/kresd_config.j2 knot-resolver-5.2.1/modules/ta_update/ta_update.test.integr/kresd_config.j2 --- knot-resolver-5.1.1/modules/ta_update/ta_update.test.integr/kresd_config.j2 2020-05-19 08:58:28.000000000 +0000 +++ knot-resolver-5.2.1/modules/ta_update/ta_update.test.integr/kresd_config.j2 2020-12-09 09:44:29.000000000 +0000 @@ -20,6 +20,7 @@ modules.unload('detect_time_skew') end +policy.add(policy.suffix(policy.PASS, {todname('test.')})) _hint_root_file('hints') cache.size = 2*MB verbose(true) diff -Nru knot-resolver-5.1.1/modules/ta_update/ta_update.test.integr/rfc5011/dns2rpl.py knot-resolver-5.2.1/modules/ta_update/ta_update.test.integr/rfc5011/dns2rpl.py --- knot-resolver-5.1.1/modules/ta_update/ta_update.test.integr/rfc5011/dns2rpl.py 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/ta_update/ta_update.test.integr/rfc5011/dns2rpl.py 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,222 @@ +#!/usr/bin/python3 +""" +Generate RFC 5011 test simulating succesfull KSK roll-over in 2017. + +Depedencies: Knot DNS server + Deckard library. +Environment: Set PYTHONPATH variable so "import pydnstest" will use module from Deckard. +Input: Root zone files, presumably created by genkeyszones.sh. +Output: RPL file for Deckard on standard output. +""" + +import copy +import datetime +import os.path +import subprocess +import time + +import dns.resolver + +import pydnstest.scenario + +try: + VARIANT = os.environ["VARIANT"] +except KeyError: + VARIANT = "" + +def store_answer(qname, qtype, template): + answ = dns.resolver.query(qname, qtype, raise_on_no_answer=False) + entr = copy.copy(template) + entr.message = answ.response + return entr + + +def resolver_init(): + """ + Configure dns.resolver to ask ::1@5353 with EDNS0 DO set. + """ + dns.resolver.reset_default_resolver() + dns.resolver.default_resolver.use_edns(0, dns.flags.DO, 4096) + dns.resolver.default_resolver.nameservers = ['::1'] + dns.resolver.default_resolver.nameserver_ports = {'::1': 5353} + dns.resolver.default_resolver.flags = 0 + + +def get_templates(): + """ + Return empty objects for RANGE and ENTRY suitable as object templates. + """ + empty_case, _ = pydnstest.scenario.parse_file(os.path.realpath('empty.rpl')) + + rng = copy.copy(empty_case.ranges[0]) + + entry = copy.copy(rng.stored[0]) + entry.adjust_fields = ['copy_id'] + entry.match_fields = ['opcode', 'question'] + + rng.addresses = {'198.41.0.4', '2001:503:ba3e::2:30'} + rng.stored = [] + + return rng, entry + + +def generate_range(filename, rng_templ, entry_templ): + """ + Run Knot DNS server with specified zone file and generate RANGE object. + """ + assert filename.startswith('20') + assert filename.endswith('.db') + try: + os.unlink('root.db') + except FileNotFoundError: + pass + os.link(filename, 'root.db') + + # run server + knotd = subprocess.Popen(['knotd', '-c', 'knot.root.conf', '-s', '/tmp/knot-dns2rpl.sock']) + time.sleep(0.1) # give kresd time to start so we do not wait for first timeout + + # query data + rng = copy.copy(rng_templ) + rng.stored = [] + rng.stored.append(store_answer('.', 'SOA', entry_templ)) + rng.stored.append(store_answer('.', 'DNSKEY', entry_templ)) + rng.stored.append(store_answer('.', 'NS', entry_templ)) + rng.stored.append(store_answer('rootns.', 'NS', entry_templ)) + rng.stored.append(store_answer('rootns.', 'A', entry_templ)) + rng.stored.append(store_answer('rootns.', 'AAAA', entry_templ)) + rng.stored.append(store_answer('test.', 'TXT', entry_templ)) + rng.a = int(filename[:-len('.db')]) + + # kill server + knotd.kill() + + return rng + + +def generate_step_query(tcurr, id_prefix): + out = '; {0}'.format(tcurr.isoformat()) + out += ''' +STEP {0}000000 QUERY +ENTRY_BEGIN +REPLY RD AD +SECTION QUESTION +test. IN TXT +ENTRY_END +'''.format(id_prefix) + return out + + +def generate_step_check(id_prefix): + return '''STEP {0}000001 CHECK_ANSWER +ENTRY_BEGIN +REPLY QR RD RA AD +MATCH opcode rcode flags question answer +SECTION QUESTION +test. IN TXT +SECTION ANSWER +test. IN TXT "it works" +ENTRY_END +'''.format(id_prefix) + +def generate_step_nocheck(id_prefix): + return '''STEP {0}000001 CHECK_ANSWER +ENTRY_BEGIN +REPLY QR RD RA AD +MATCH opcode qname question +SECTION QUESTION +test. IN TXT +SECTION ANSWER +test. IN TXT "it works" +ENTRY_END +'''.format(id_prefix) + +def generate_step_finish_msg(id_prefix): + return '''STEP {0}000001 CHECK_ANSWER +ENTRY_BEGIN +REPLY QR RD RA AA NXDOMAIN +MATCH opcode rcode flags question answer +SECTION QUESTION +test. IN TXT +SECTION AUTHORITY +test. 10800 IN SOA test. nobody.invalid. 1 3600 1200 604800 10800 +SECTION ADDITIONAL +explanation.invalid. 10800 IN TXT "check last answer" +ENTRY_END +'''.format(id_prefix) + +def generate_step_elapse(tstep, id_prefix): + out = '; move time by {0}\n'.format(tstep) + out += '''STEP {0}000099 TIME_PASSES ELAPSE {1}\n\n'''.format( + id_prefix, int(tstep.total_seconds())) + return out + + +def main(): + resolver_init() + rng_templ, entry_templ = get_templates() + ranges = [] + check_last_msg = False + + # transform data in zones files into RANGEs + files = os.listdir() + files.sort() + for fn in files: + if not fn.endswith('.db') or not fn.startswith('20'): + continue + ranges.append(generate_range(fn, rng_templ, entry_templ)) + + # connect ranges + for i in range(1, len(ranges)): + ranges[i - 1].b = ranges[i].a - 1 + ranges[-1].b = 99999999999999 + + # steps + steps = [] + tstart = datetime.datetime(year=2017, month=7, day=1) + if VARIANT == "unmanaged_key": + tend = datetime.datetime(year=2017, month=7, day=21, hour=23, minute=59, second=59) + check_last_msg = True + else: + tend = datetime.datetime(year=2017, month=12, day=31, hour=23, minute=59, second=59) + tstep = datetime.timedelta(days=1) + tcurr = tstart + while tcurr < tend: + id_prefix = tcurr.strftime('%Y%m%d') + steps.append(generate_step_query(tcurr, id_prefix)) + if (check_last_msg is True and tcurr + tstep > tend): + steps.append(generate_step_finish_msg(id_prefix)) + elif VARIANT == "unmanaged_key": + steps.append(generate_step_nocheck(id_prefix)) + else: + steps.append(generate_step_check(id_prefix)) + steps.append(generate_step_elapse(tstep, id_prefix)) + tcurr += tstep + + # generate output + with open('keys/ds') as dsfile: + tas = dsfile.read().strip() + + # constant RPL file header + print("stub-addr: 2001:503:ba3e::2:30") + for ta in tas.split('\n'): + print ("trust-anchor: " + ta) + print("""val-override-date: 20170701000000 +query-minimization: off +CONFIG_END + +SCENARIO_BEGIN Simulation of successfull RFC 5011 KSK roll-over during 2017 + """.format(ta=ta)) + for rng in ranges: + print(rng) + + for step in steps: + print(step) + + # constant RPL file footer + print(''' +SCENARIO_END + ''') + + +if __name__ == '__main__': + main() diff -Nru knot-resolver-5.1.1/modules/ta_update/ta_update.test.integr/rfc5011/empty.rpl knot-resolver-5.2.1/modules/ta_update/ta_update.test.integr/rfc5011/empty.rpl --- knot-resolver-5.1.1/modules/ta_update/ta_update.test.integr/rfc5011/empty.rpl 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/ta_update/ta_update.test.integr/rfc5011/empty.rpl 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,20 @@ +stub-addr: 127.0.0.10 +CONFIG_END + +SCENARIO_BEGIN empty replies + +RANGE_BEGIN 0 100 + ADDRESS 127.0.0.10 +ENTRY_BEGIN +MATCH subdomain +ADJUST copy_id copy_query +SECTION QUESTION +. IN A +ENTRY_END +RANGE_END + +STEP 1 QUERY +ENTRY_BEGIN +ENTRY_END + +SCENARIO_END diff -Nru knot-resolver-5.1.1/modules/ta_update/ta_update.test.integr/rfc5011/genkeyszones.sh knot-resolver-5.2.1/modules/ta_update/ta_update.test.integr/rfc5011/genkeyszones.sh --- knot-resolver-5.1.1/modules/ta_update/ta_update.test.integr/rfc5011/genkeyszones.sh 1970-01-01 00:00:00.000000000 +0000 +++ knot-resolver-5.2.1/modules/ta_update/ta_update.test.integr/rfc5011/genkeyszones.sh 2020-12-09 09:44:29.000000000 +0000 @@ -0,0 +1,174 @@ +#!/usr/bin/bash + +# First, generate DNSSEC keys with timers set to simulate 2017 KSK roll-over. +# Second, fake system time to pretend that we are at the beginning on time slots +# used during 2017 and sign our fake root zone. + +# Depends on libfaketime + dnssec-keygen and dnssec-signzone from BIND 9.11. + +# Output: Bunch of DNSSEC keys + several versions of signed root zone. + +set -o nounset -o errexit -o xtrace + +GEN="dnssec-keygen -K keys/ -a RSASHA256 -b 2048 -L 21d" + +function usage { + echo -e "Usage: $0